Java-基于 Instrument 的 Agent
Agent 为 JVMTI 的客户端。 这里记录的是基于Java Instrument 的 Agent 实现,还有直接基于 JVMTI 的 Agent 实现。 在 JDK1.5 以后,我们可以使用 Agent 技术构建一个独立于应用程序的代理程序,用来协助监测、运行甚至替换其他 JVM 上的程序。使用它可以实现虚拟机级别的 AOP 功能。 Agent 分为两种,一种是在主程序之前运行的 Agent,一种是在主程序之后运行的 Agent(JDK1.6 以后)。 ? 一、在主程序运行之前的代理程序1.编写 agent 程序package before; import java.lang.instrument.Instrumentation; public class AgentApplication { public static void premain(String arg,Instrumentation instrumentation) { System.err.println("agent startup,args is " + arg); } } 2.添加?MANIFEST.MF 文件路径为 META-INF/MANIFEST.MF Manifest-Version: 1.0 Premain-Class: before.AgentApplication Can-Redefine-Classes: true Can-Retransform-Classes: true 若使用的是 Maven 编译就不用手动添加,配置 pom.xml 即可 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Premain-Class>before.AgentApplication</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build> 3.运行新建一个测试类并编译为 class 文件 package com; public class Main { public static void main(String[] args) { System.out.println("123"); } } 使用命令运行 java -javaagent:..mahout-1.0-SNAPSHOT.jar=abc com.Main 可以看到?premain 方法在 main 之前运行了 ? 二、在主程序运行之后的代理程序关于动态 attach:https://openjdk.java.net/groups/hotspot/docs/Serviceability.html#battach https://juejin.im/post/5b0d020d518825153f10403f 1.编写 agent 程序由于是在主程序运行后再执行,意味着我们可以获取主程序运行时的信息,这里我们打印出来主程序中加载的类名。 package after; import java.lang.instrument.Instrumentation; public class AgentApplication { public static void agentmain(String arg,args is " + arg); Class<?>[] classes = instrumentation.getAllLoadedClasses(); for (Class<?> cls : classes) { System.out.println(cls.getName()); } } } 2.添加?MANIFEST.MF 文件路径为 META-INF/MANIFEST.MF Manifest-Version: 1.0 Agent-Class: after.AgentApplication Can-Redefine-Classes: true Can-Retransform-Classes: true 若使用的是 Maven 编译就不用手动添加,配置 pom.xml 即可 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Agent-Class>after.AgentApplication</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build> 3.运行由于是在程序运行之后运行,需要先有一个 Java 进程,直接用 IDE 运行即可,不用使用命令行。 package com; public class Main { public static void main(String[] args) throws InterruptedException { for (; ; ) { System.out.println("123"); Thread.sleep(1000); } } } 运行后查看改程序的进程号,这里为 9084 然后将?agent 程序附加到上面程序的进程中 com.sun.tools.attach.VirtualMachine 在 JAVA_HOME 路径下 lib/tools.jar 中,如果 IDE 报找不到,可以手动将?tools.jar 添加进来,或配置?CLASSPATH 环境变量。 package com; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; import java.io.IOException; public class Attach { public static void main(String[] args) throws IOException,AttachNotSupportedException,AgentLoadException,AgentInitializationException { VirtualMachine vm = VirtualMachine.attach("9084"); vm.loadAgent("D:IDEACodeLibjhxxbmahouttargetmahout-1.0-SNAPSHOT.jar"); } } 直接 IDE 运行即可,执行完毕后回到 Main 程序的打印控制台 ? 三、使用相关其中 -javaagent 可以有多个,但如果把 -javaagent 放在 -jar 后面,则不会生效。也就是放在主程序后面的 agent 是无效的。 如:java -javaagent:D:myagent-1.jar=ABC -javaagent:D:myagent-2.jar=DEF -jar myapp.jar -javaagent:D:myagent-3.jar=GHI,其中 myagent-3.jar 是无效的。 ? 其中 premain(agentmain) 方法有两个:
JVM 会优先加载 1,加载成功则忽略 2,如没有 1,则加载 2。加载逻辑在 sun.instrument.InstrumentationImpl 类中: private void loadClassAndStartAgent(String classname,String methodname,String optionsString) throws Throwable { ClassLoader mainAppLoader = ClassLoader.getSystemClassLoader(); Class<?> javaAgentClass = mainAppLoader.loadClass(classname); Method m = null; NoSuchMethodException firstExc = null; boolean twoArgAgent = false; // The agent class must have a premain or agentmain method that // has 1 or 2 arguments. We check in the following order: // // 1) declared with a signature of (String,Instrumentation) // 2) declared with a signature of (String) // 3) inherited with a signature of (String,Instrumentation) // 4) inherited with a signature of (String) // // So the declared version of either 1-arg or 2-arg always takes // primary precedence over an inherited version. After that,the // 2-arg version takes precedence over the 1-arg version. // // If no method is found then we throw the NoSuchMethodException // from the first attempt so that the exception text indicates // the lookup failed for the 2-arg method (same as JDK5.0). try { m = javaAgentClass.getDeclaredMethod(methodname,new Class<?>[]{String.class,java.lang.instrument.Instrumentation.class}); twoArgAgent = true; } catch (NoSuchMethodException x) { // remember the NoSuchMethodException firstExc = x; } if (m == null) { // now try the declared 1-arg method try { m = javaAgentClass.getDeclaredMethod(methodname,new Class<?>[]{String.class}); } catch (NoSuchMethodException x) { // ignore this exception because we‘ll try // two arg inheritance next } } if (m == null) { // now try the inherited 2-arg method try { m = javaAgentClass.getMethod(methodname,java.lang.instrument.Instrumentation.class}); twoArgAgent = true; } catch (NoSuchMethodException x) { // ignore this exception because we‘ll try // one arg inheritance next } } if (m == null) { // finally try the inherited 1-arg method try { m = javaAgentClass.getMethod(methodname,new Class<?>[]{String.class}); } catch (NoSuchMethodException x) { // none of the methods exists so we throw the // first NoSuchMethodException as per 5.0 throw firstExc; } } // the premain method should not be required to be public,// make it accessible so we can call it // Note: The spec says the following: // The agent class must implement a public static premain method... setAccessible(m,true); // invoke the 1 or 2-arg method if (twoArgAgent) { m.invoke(null,new Object[]{optionsString,this}); } else { m.invoke(null,new Object[]{optionsString}); } // don‘t let others access a non-public premain method setAccessible(m,false); } ? Instrument premain、agentmain 方法执行时机:premain 执行时机:在 JVM 启动时(所有的 Java 类都未被初始化,所有的对象实例都未被创建),初始化函数 eventHandlerVMinit 会调用 sun.instrument.instrumentationImpl 类的 loadClassAndCallPremain 方法去执行 Premain-Class 指定类的 premain 方法。 agentmain 执行时机:在 JVM 启动后,通过 VirtualMachine 附着一个 Instrument,如:vm.loadAgent(jar),会调用 sun.instrument.instrumentationImpl 类的 loadClassAndCallAgentmain 方法去执行 Agentmain-Class 指定类的 agentmain 方法。 ? premain、agentmain 方法中两个参数:agentArgs:代理程序命令行中输入参数,随同 “-javaagent” 一起传入,与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组。 inst:java.lang.instrument.Instrumentation 实例,由 JVM 自动传入,集中了几乎所有功能方法,如:类操作、classpath 操作等。 ? META-INF/MAINFEST.MF 参数:
? https://www.jianshu.com/p/63c328ca208d https://yq.aliyun.com/articles/658806 https://www.jianshu.com/p/9f4e8dcb3e2f https://juejin.im/post/5b0925ec51882538aa1ee248 https://www.ibm.com/developerworks/cn/java/j-lo-jpda2 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |