博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java Attach API
阅读量:5218 次
发布时间:2019-06-14

本文共 11583 字,大约阅读时间需要 38 分钟。

catalog

1. instrucment与Attach API2. 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: TestAgentPremain-Class: TestAgentCan-Redine-Classes: trueCan-Retransform-Classes: true

用jar命令将TestAgent打包

1. 编译TestAgentjavac TestAgent.java2. jar打包jar cvmf MANIFEST.MF xxx.jar TestAgent.class

启动TestMain,并设置javaagent参数

1. 编译TestMainjavac TestMain.java 2. 启动TestMainjava -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: TestAgentPremain-Class: TestAgentCan-Redine-Classes: trueCan-Retransform-Classes: true

将类打包为jar包

1. 编译TestAgentjavac TestAgent.java2. 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
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);  //加载AgentString 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.htmlhttp://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/index.html

 

Copyright (c) 2015 LittleHann All rights reserved

 

转载于:https://www.cnblogs.com/LittleHann/p/4783581.html

你可能感兴趣的文章
JavaScript 鸭子模型
查看>>
PHP典型功能与Laravel5框架开发学习笔记
查看>>
SQL Server 如何查询表定义的列和索引信息
查看>>
项目上传到github上
查看>>
GCD 之线程死锁
查看>>
NoSQL数据库常见分类
查看>>
JS小工具_字符串转16进制数组_02
查看>>
信息安全系统设计基础实验四—20135214万子惠20135227黄晓妍
查看>>
一题多解 之 Bat
查看>>
Java 内部类
查看>>
PHP程序员的技术成长规划
查看>>
测试一个对象是否是类字符串
查看>>
{面试题7: 使用两个队列实现一个栈}
查看>>
用JSONP方式跨域请求
查看>>
[Luogu 4135] 作诗
查看>>
[转]SQL中 OVER(PARTITION BY) 取上一条,下一条等
查看>>
前端开发就从认识浏览器开始 - 浏览器处理请求的过程
查看>>
【练习】使用事务和锁定语句
查看>>
centos7升级firefox的flash插件
查看>>
jmeter系列二(jmeter engine相关)
查看>>