Java Attach API

catalog

1. instrucment与Attach API
2. BTrace: VM Attach的两种方式
3. Sun JVM Attach API

1. instrucment与Attach API

JDK5中增加了一个包java.lang.instrucment,能够对JVM底层组件进行访问。在JDK 5中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作
?在Java5中,开发基于Instrucment的应用,需要以下几个步骤

1. 编写premain函数
?2. jar文件打包
?3. 运行agent 

但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了instrument的应用。而Java SE 6的新特性改变了这种情况,通过Java Tool API中的attach方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到instrumentation的目的
?在JDK6中,针对这点做了改进,开发者可以在main开始执行以后,再开启自己的Instrucment程序
Attach API不是Java的标准API,而是Sun公司提供的一套扩展API,用来向目标JVM"附着"(Attach)代理工具程序的。有了它,开发者可以方便的监控一个JVM,运行一个外加的代理程序,Sun JVM Attach API功能上非常简单,仅提供了如下几个功能

1. 列出当前所有的JVM实例描述
2. Attach到其中一个JVM上,建立通信管道
3. 让目标JVM加载Agent

Relevant Link:

http://iamzhongyong.iteye.com/blog/1843558

2. BTrace: VM Attach的两种方式

BTrace的特点之一就是可以动态Attach到一个运行的JVM进程上,然后根据BTrace脚本来对目标JVM进行相应的操作
JVM的 Attach有两种方式

1. 指定javaagent参数
2. 运行时动态attach

0x1: 指定javaagent参数

这种方式的特点就是在目标JVM启动时,就确定好了要加载什么样的代理对象,例如

java -javaagent:xxxx.jar TestMain

TestMain.java

package test;

public class TestMain
{
    public static void main(String[] args) throws InterruptedException
    {
        System.out.println("Hello");
    }

}

TestAgent.java

package test;

import java.lang.instrument.Instrumentation;
import java.io.*;

public class TestMain
{
    public static void agentmain(String args, Instrumentation inst) throws Exception
    {
        System.out.println("Args:" + args);
    }

    public static void premain(String args, Instrumentation inst) throws Exception
    {
        System.out.println("Pre Args:" + args);
        Class[] classes = inst.getAllLoadedClasses();
        for (Class clazz : classes)
        {
           System.out.println(clazz.getName());
        }
    }
}

TestAgent类比较简单,最终它会在目标类的Main方法执行之前,执行premain方法,其主要动作是将以及加载的类打印出来。 我们需要将这个类打包成jar文件,以便在目标JVM启动时候,以参数形式指定给它。打成jar的同时,设定MANIFEST.MF文件的内容。告知目标JVM该如何处理

Agent-Class: TestAgent
Premain-Class: TestAgent
Can-Redine-Classes: true
Can-Retransform-Classes: true

用jar命令将TestAgent打包

1. 编译TestAgent
javac TestAgent.java

2. jar打包
jar cvmf MANIFEST.MF xxx.jar TestAgent.class

启动TestMain,并设置javaagent参数

1. 编译TestMain
javac TestMain.java 

2. 启动TestMain
java -javaagent:xxx.jar TestMain

0x2: 动态Attach,load指定Agent

这种方式与之前指定参数的不同在于,其可以在JVM已经运行的情况下,动态的附着上去,并可以动态加载agent
TestMain.java

public class TestMain
{
    public static void main(String[] args) throws InterruptedException
    {
        while(true)
        {
            Thread.sleep(10000);
            new Thread(new WaitThread()).start();
        }
    }  

   static class WaitThread implements Runnable
   {
        @Override
        public void run()
        {
            System.out.println("Hello");
        }
   }
}

TestAgent.java

import java.lang.instrument.Instrumentation;
import java.io.*;

public class TestAgent
{
    public static void agentmain(String args, Instrumentation inst) throws Exception
    {
        System.out.println("Args:" + args);
    }

    public static void premain(String args, Instrumentation inst) throws Exception
    {
        System.out.println("Pre Args:" + args);
        Class[] classes = inst.getAllLoadedClasses();
        for (Class clazz : classes)
        {
           System.out.println(clazz.getName());
        }
    }
}

动态加载agent的情况下,被调用的是agentmain方法, 其会在JVMload的时候,被调用
MANIFEST.MF

Agent-Class: TestAgent
Premain-Class: TestAgent
Can-Redine-Classes: true
Can-Retransform-Classes: true

将类打包为jar包

1. 编译TestAgent
javac TestAgent.java

2. jar打包
jar cvmf MANIFEST.MF xxx.jar TestAgent.class

动态附着到对应的JVM需要使用到JDK的Attach API
Main.java

import com.sun.tools.attach.VirtualMachine;

public class Main
{
  public static void main(String[] args) throws Exception
  {
    VirtualMachine vm = null;
    String agentjarpath = "C:/Users/zhenghan.zh/Desktop/新建文件夹/xxx.jar"; //agentjar路径
    vm = VirtualMachine.attach("9730");//目标JVM的进程ID(PID)
    vm.loadAgent(agentjarpath, "This is Args to the Agent.");
    vm.detach();
  }
}

一旦运行这个Main方法, 其就会动态的附着到我们对应的JVM进程中,并为目标JVM加载我们指定的Agent,以达到我们想做的事情, 比如BTrace就为在附着到目标JVM后,开启一个ServerSocket,以便达到与目标进程通讯的目的

Relevant Link:

http://ivanzhangwb.github.io/btrace-vm-attach-api/ 

3. Sun JVM Attach API

Sun JVM Attach API是Sun JVM中的一套非标准的可以连接到JVM上的API,从JDK6开始引入,除了Solaris平台的Sun JVM支持远程的Attach,在其他平台都只允许Attach到本地的JVM上

0x1: 列出当前所有的JVM实例描述

package test;
import java.util.List;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

public class Test
{

    public static void main(String[] args)
    {
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vmd : list)
        {
            System.out.println("pid:" + vmd.id() + ":" + vmd.displayName());
        }
    }

}
//tools.jar needs to be added to the IDE‘s library path and the program‘s classpath. The tools.jar file is found in the JDK‘s lib directory.

0x2: Attach到特定进程的JVM上,并加载Agent

//Attach到JVM上
VirtualMachine virtualmachine = VirtualMachine.attach(pid);
//加载Agent
String javaHome = virtualmachine.getSystemProperties().getProperty("java.home");
String agentPath = javaHome + File.separator + "jre" + File.separator + "lib" + File.separator + "management-agent.jar");
File file = new File(agentPath);
if(!file.exists())
{
     agentPath = javaHome + File.separator + "lib" + File.separator + "management-agent.jar";
      file = new File(agentPath);
      if(!file.exists())
          throw new IOException("Management agent not found");
      }
}  

agentPath = file.getCanonicalPath();
try
{
     virtualmachine.loadAgent(agentPath, "com.sun.management.jmxremote");
}
catch(AgentLoadException e)
{
     throw new IOException(e);
}
catch(AgentInitializationException agentinitializationexception)
{
     throw new IOException(e);
}
Properties properties = virtualmachine.getAgentProperties();
address = (String)properties.get("com.sun.management.jmxremote.localConnectorAddress");
virtualmachine.detach(); 

0x3: Attach API底层实现(windows)

\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsAttachProvider.java

public VirtualMachine attachVirtualMachine(String vmid) throws AttachNotSupportedException, IOException
{
    checkAttachPermission();

    // AttachNotSupportedException will be thrown if the target VM can be determined
    // to be not attachable.
    testAttachable(vmid);

    return new WindowsVirtualMachine(this, vmid);
}

\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsVirtualMachine.java

WindowsVirtualMachine(AttachProvider provider, String id) throws AttachNotSupportedException, IOException
{
    //继承HotSpotVirtualMachine
    super(provider, id);

    int pid;
    try
    {
        pid = Integer.parseInt(id);
    }
    catch (NumberFormatException x)
    {
        throw new AttachNotSupportedException("Invalid process identifier");
    }
    //先连接上目标JVM
    hProcess = openProcess(pid);

    // The target VM might be a pre-6.0 VM so we enqueue a "null" command
    // which minimally tests that the enqueue function exists in the target
    // VM.
    try
    {
        enqueue(hProcess, stub, null, null);
    }
    catch (IOException x)
    {
        throw new AttachNotSupportedException(x.getMessage());
    }
}

WindowsVirtualMachine继承HotSpotVirtualMachine,先看看HotSpotVirtualMachine的loadAgent方法
\openjdk\jdk\src\share\classes\sun\tools\attach\HotSpotVirtualMachine.java

/*
* Load JPLIS agent which will load the agent JAR file and invoke
* the agentmain method.
*/
public void loadAgent(String agent, String options) throws AgentLoadException, AgentInitializationException, IOException
{
    String args = agent;
    if (options != null)
    {
        args = args + "=" + options;
    }
    try
    {
        loadAgentLibrary("instrument", args);
    }
    catch (AgentLoadException x)
    {
        throw new InternalError("instrument library is missing in target VM");
    }
    catch (AgentInitializationException x)
    {
        /*
         * Translate interesting errors into the right exception and
         * message (FIXME: create a better interface to the instrument
         * implementation so this isn‘t necessary)
         */
        int rc = x.returnValue();
        switch (rc)
        {
        case JNI_ENOMEM:
            throw new AgentLoadException("Insuffient memory");
        case ATTACH_ERROR_BADJAR:
            throw new AgentLoadException("Agent JAR not found or no Agent-Class attribute");
        case ATTACH_ERROR_NOTONCP:
            throw new AgentLoadException("Unable to add JAR file to system class path");
        case ATTACH_ERROR_STARTFAIL:
            throw new AgentInitializationException("Agent JAR loaded but agent failed to initialize");
        default :
            throw new AgentLoadException("Failed to load agent - unknown reason: " + rc);
        }
    }
}

loadAgentLibrary("instrument", args);

/*
* Load agent library
* If isAbsolute is true then the agent library is the absolute path
* to the library and thus will not be expanded in the target VM.
* if isAbsolute is false then the agent library is just a library
* name and it will be expended in the target VM.
*/
private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options) throws AgentLoadException, AgentInitializationException, IOException
{
    InputStream in = execute("load",
                 agentLibrary,
                 isAbsolute ? "true" : "false",
                 options);
    try
    {
        int result = readInt(in);
        if (result != 0)
        {
        throw new AgentInitializationException("Agent_OnAttach failed", result);
        }
    }
    finally
    {
        in.close();

    }
}

可以看到,Java在Attach到目标进行后,调用execute让目标进行加载Agent类,我们继续分析execute的实现方式,可以看到,JVM进程间通信是JVM Attach API的核心,JVM自身就预留了执行来自Attach进程的指令接口
\openjdk\jdk\src\windows\classes\sun\tools\attach\WindowsVirtualMachine.java

InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException
{
    assert args.length <= 3;        // includes null

    // create a pipe using a random name
    int r = (new Random()).nextInt();
    String pipename = "\\\\.\\pipe\\javatool" + r;
    long hPipe = createPipe(pipename);

    // check if we are detached - in theory it‘s possible that detach is invoked
    // after this check but before we enqueue the command.
    if (hProcess == -1)
    {
        closePipe(hPipe);
        throw new IOException("Detached from target VM");
    }

    try
    {
        // enqueue the command to the process
        enqueue(hProcess, stub, cmd, pipename, args);

        // wait for command to complete - process will connect with the
        // completion status
        connectPipe(hPipe);

        // create an input stream for the pipe
        PipedInputStream is = new PipedInputStream(hPipe);

        // read completion status
        int status = readInt(is);
        if (status != 0)
        {
        // special case the load command so that the right exception is thrown
        if (cmd.equals("load"))
        {
            throw new AgentLoadException("Failed to load agent library");
        }
        else
        {
            throw new IOException("Command failed in target VM");
        }
        }

        // return the input stream
        return is;

    }
    catch (IOException ioe)
    {
        closePipe(hPipe);
        throw ioe;
    }
}

JVM的execute方法中调用了大量native方法,并且从代码中可以看出,JVM Attach的进程间通信使用了管道进行通信

Relevant Link:

http://ayufox.iteye.com/blog/655761
http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html
http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/index.html 

Copyright (c) 2015 LittleHann All rights reserved

时间: 2024-10-14 11:04:58

Java Attach API的相关文章

全面挖掘Java Excel API 使用方法

使用Windows操作系统的朋友对Excel(电子表格)一定不会陌生,但是要使用Java语言来操纵Excel文件并不是一件容易的事.在Web应用日益盛行的今天,通过Web来操作Excel文件的需求越来越强烈,目前较为流行的操作是在JSP或Servlet 中创建一个CSV (comma separated values)文件,并将这个文件以MIME,text/csv类型返回给浏览器,接着浏览器调用Excel并且显示CSV文件.这样只是说可以访问到Excel文件,但是还不能真正的操纵Excel文件,

新增的Java MapReduce API

http://book.51cto.com/art/201106/269647.htm Hadoop的版本0.20.0包含有一个新的 Java MapReduce API,有时也称为"上下文对象"(context object),旨在使API在今后更容易扩展.新的API 在类型上不兼容先前的API,所以,需要重写以前的应用程序才能使新的API发挥作用. 新增的API 和旧的API 之间,有下面几个明显的区别. 新的API 倾向于使用虚类,而不是接口,因为这更容易扩展.例如,可以无需修改

Atitit. C# java 的api 目录封装结构映射总结

Atitit. C#  java 的api 目录封装结构映射总结 C# java ref System.Reflection System.Type, java.lang.ref concurrent thread System.Threading java.lang.Thread collection System.collection java.util.collection Io Sys.io sys.text sys.xml sys.data.sql sys.data sys.web s

Java 2D API - 2. Graphics 入门

Java 2D API强大而复杂,不过大多时候我们只需使用java.awt.Graphcis类的部分功能.下面的内容将覆盖大多数的常见应用. Graphics 类中的方法大致可以分为两类: Draw and fill方法,用于绘制基本的图形.文本和图像: 属性设置方法,用于控制绘制和填充的效果.setFont()和setColor()等方法就属于这类方法. 下图显示这些方法和图像的对应关系: 1. drawString() 用于绘制文本: g.drawString("Hello", 1

Java 2D API - 1. 基本概念

Java 2D API扩展AWT包,对二维图形.文本及成像功能提供了支持,可用于开发复杂的界面.绘图软件和图像编辑器.Java 2D对象位于用户坐标空间(User coordinate space),当对象呈现在屏幕或打印机,用户空间坐标转换为设备空间坐标(device space coordinate).下面两个类对 Java 2D API十分重要: Graphics Graphics2D Java 2D API提供了如下功能: 为显示设备和打印机等提供了统一的渲染模型: 提供了大量的几何图元

Java Logging API - Tutorial

Java Logging This article describes how to use the Logging API in Java programs. It includes an example for creating an HTML logger. Table of Contents 1. Overview 1.1. Logging 1.2. Logging in Java 1.3. Create a logger 1.4. Level 1.5. Handler 1.6. For

浅析Java servlet Api

Java Servlet 开发工具(JSDK)提供了多个软件包,在编写 Servlet 时需要用到这些软件包.其中包括两个用于所有 Servlet 的基本软件包:javax.servlet 和 javax.servlet.http.可从sun公司的Web站点下载 Java Servlet 开发工具. 下面主要介绍javax.servlet.http提供的HTTP Servlet应用编程接口. HTTP Servlet 使用一个 HTML 表格来发送和接收数据.要创建一个 HTTP Servlet

Java Servlet API中文说明文档

Java Servlet API中文说明文档译者前言:       近来在整理有关Servlet资料时发现,在网上竟然找不到一份中文的Java Servlet API的说明文档,而在有一本有关JSP的书后面附的Java Servlet API说明竟然不全,而这份文档的2.1a版在1998年的11月份就已定稿.所以我决定翻译一份中文的文档(其中一些与技术关系不大的部分已被略去),有兴趣的读者可以从http: //java.sun.com/products/servlet/2.1/servletsp

Java Excel API简介

1.从Excel文件读取数据表 Java Excel API既可以从本地文件系统的一个文件(.xls),也可以从输入流中读取Excel数据表.读取Excel数据表的第一步是创建Workbook(术语:工作薄),下面的代码片段举例说明了应该如何操作:(完整代码见ExcelReading.java) import java.io.*; import jxl.*; … … … … try { //构建Workbook对象, 只读Workbook对象 //直接从本地文件创建Workbook //从输入流