JDK5中增长了一个包java.lang.instrucment,可以对JVM底层组件进行访问。在JDK 5中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入以前),instrumentation的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载状况,并完成实际工做
在Java5中,开发基于Instrucment的应用,须要如下几个步骤html
可是在实际的不少的状况下,咱们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了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功能上很是简单,仅提供了以下几个功能java
Relevant Link:git
http://iamzhongyong.iteye.com/blog/1843558
BTrace的特色之一就是能够动态Attach到一个运行的JVM进程上,而后根据BTrace脚原本对目标JVM进行相应的操做
JVM的 Attach有两种方式github
1. 指定javaagent参数 2. 运行时动态attach
这种方式的特色就是在目标JVM启动时,就肯定好了要加载什么样的代理对象,例如windows
java -javaagent:xxxx.jar TestMain
TestMain.javaapi
package test; public class TestMain { public static void main(String[] args) throws InterruptedException { System.out.println("Hello"); } }
TestAgent.javaoracle
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该如何处理dom
Agent-Class: TestAgent Premain-Class: TestAgent Can-Redine-Classes: true Can-Retransform-Classes: true
用jar命令将TestAgent打包ide
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
这种方式与以前指定参数的不一样在于,其能够在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/
Sun JVM Attach API是Sun JVM中的一套非标准的能够链接到JVM上的API,从JDK6开始引入,除了Solaris平台的Sun JVM支持远程的Attach,在其余平台都只容许Attach到本地的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.
//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();
\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