javaagent技术&Attach技术
之前见过好多种-javaagent 参数,比如我们IDEA启动一个类的时候就会有好多的javaagent。 好像又叫探针技术,简单研究下其过程。
Java 5 中提供的 Instrument 包启动时往 Java 虚拟机中挂上一个用户定义的 hook 程序,可以在装入特定类的时候改变特定类的字节码,从而改变该类的行为。Instrument 包是在整个虚拟机上挂了一个钩子程序,每次装入一个新类的时候,都必须执行一遍这段程序,即使这个类不需要改变。一个核心类是sun.instrument.InstrumentationImpl, 这个类可以动态的增加转换器或者获取当前JVM加载的所有的类信息。
参考: https://www.jacoco.org/jacoco/trunk/doc/agent.html
第一种: 使用 premain 可以在类第一次加载之前修改类信息,加载之后修改需要重新创建类加载器, premain是Java SE5开始就提供的代理方式。而且使用时必须在命令行指定代理jar,并且代理类必须在main方法前启动。
第二种: Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。
1. premain 使用
1. 简单使用
1. agent.MyAgent
package agent; import java.lang.instrument.Instrumentation; public class MyAgent { /** * JVM 在类加载前会调用到此函数 * * @param agentOps * @param inst */ public static void premain(String agentOps, Instrumentation inst) { System.out.println("agent.MyAgent.premain start "); System.out.println(agentOps); System.out.println(inst); System.out.println("agent.MyAgent.premain end "); } }
2. 编写META-INF/MANIFEST.MF
Manifest-Version: 1.0 Can-Retransform-Classes: true Premain-Class: agent.MyAgent
这个配置文件需要注意格式,如果之前打过jar 包应该会注意。 最后有个空行, 每个key后面的冒号 和 value 之间有个空格
3. 最后的目录结构如下:
$ ls -R .: agent/ META-INF/ ./agent: MyAgent.class ./META-INF: MANIFEST.MF
4. 生成jar 包
D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./ 已添加清单 正在添加: agent/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: agent/MyAgent.class(输入 = 754) (输出 = 415)(压缩了 44%) 正在忽略条目META-INF/ 正在忽略条目META-INF/MANIFEST.MF
5. 新建测试类:
public class PlainTest { public static void main(String[] args) throws InterruptedException { new PlainTest().test(); } public void test() throws InterruptedException { Thread.sleep(5 * 1000); System.out.println("cn.qz.PlainTest.test\t" + 111222); } }
6. 编译运行测试:
$ java -javaagent:D:/agentjar/agent.jar PlainTest agent.MyAgent.premain start null sun.instrument.InstrumentationImpl@5fe5c6f agent.MyAgent.premain end cn.qz.PlainTest.test 111222
测试传递参数: (可以传递参数给指定的方法, 方法内部也可以根据参数进行一些特殊的处理)
$ java -javaagent:D:/agentjar/agent.jar=key1=value1,key2=value2 PlainTest agent.MyAgent.premain start key1=value1,key2=value2 sun.instrument.InstrumentationImpl@5fe5c6f agent.MyAgent.premain end cn.qz.PlainTest.test 111222
7. 使用IDEA 的方式进行调试
同样的jar 包指定使用之前打的jar,和探针对应的类可以在DIEA 中使用java 文件进行调试。
(1) 目录结构
(2) agent.MyAgent
package agent; import java.lang.instrument.Instrumentation; public class MyAgent { /** * JVM 在类加载前会调用到此函数 * * @param agentOps * @param inst */ public static void premain(String agentOps, Instrumentation inst) { System.out.println("agent.MyAgent.premain XXX start "); System.out.println(agentOps); System.out.println(inst); System.out.println("agent.MyAgent.premain XXX end "); } }
(3) 增加测试类
package cn.qz; public class PlainTest { public static void main(String[] args) throws InterruptedException { new PlainTest().test(); } public void test() throws InterruptedException { Thread.sleep(5 * 1000); System.out.println("cn.qz.PlainTest.test\t" + 111222); } }
(4) 编辑增加代理 idea 中 Add VM Operations 增加参数: -javaagent:D:\agentjar\agent.jar
(5) 测试查看运行结果:
agent.MyAgent.premain XXX start null sun.instrument.InstrumentationImpl@1eb44e46 agent.MyAgent.premain XXX end cn.qz.PlainTest.test 111222
(6) debug 到agent.MyAgent#premain 方法内部, 查看调用链:
2. premain 实现监测方法执行时间的操作
基于javassit 对字节码进行增强。
1. pom 增加
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency>
2. agent.MyAgent 源码
package agent; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; public class MyAgent { /** * 有该方法会优先执行该方法 * * @param agentOps * @param inst */ public static void premain(String agentOps, Instrumentation inst) { System.out.println("====premain 方法执行"); System.out.println(agentOps); System.out.println(inst); System.out.println("====premain2 方法执行"); /** * 添加一个转换器, 字节码加载到虚拟机前会调用此类的transform 方法 */ inst.addTransformer(new ClassFileTransformer() { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (!className.startsWith("cn/qz")) { return null; } System.out.println("ClassLoader : " + loader); System.out.println("className : " + className); //创建类,这是一个单例对象 ClassPool cp = ClassPool.getDefault(); //我们需要构建的类 try { CtClass ctClass = cp.get(className.replace("/", ".")); CtMethod[] declaredMethods = ctClass.getDeclaredMethods(); for (CtMethod method : declaredMethods) { // 修改方法体来实现, 增加两个局部变量用于记录执行时间 method.addLocalVariable("startTimeAgent", CtClass.longType); method.insertBefore("startTimeAgent = System.currentTimeMillis();"); method.addLocalVariable("methodNameAgent", cp.get(String.class.getName())); method.insertBefore("methodNameAgent = \"" + method.getLongName() + "\";"); method.insertAfter("System.out.println(methodNameAgent + \" exec time is :\" + (System.currentTimeMillis() - startTimeAgent) + \"ms\");"); } return ctClass.toBytecode(); } catch (Exception e) { e.printStackTrace(); } return null; } }); } }
3. 使用上面测试类进行测试查看日志
====premain 方法执行 null sun.instrument.InstrumentationImpl@1eb44e46 ====premain2 方法执行 ClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2 className : cn/qz/PlainTest cn.qz.PlainTest.test 111222 cn.qz.PlainTest.test() exec time is :5001ms cn.qz.PlainTest.main(java.lang.String[]) exec time is :5002ms
4. 打包:
(1) 下载pom 依赖的jar 包, 进入项目目录(有pom.xml的目录),cmd执行如下命令
mvn dependency:copy-dependencies -DoutputDirectory=dependency_lib
(2) 构造目录结构:
$ ls -R .: agent/ dependency_lib/ META-INF/ ./agent: 'MyAgent$1.class' MyAgent.class ./dependency_lib: javassist-3.28.0-GA.jar ./META-INF: MANIFEST.MF
(3) 修改MANIFEST.MF
Manifest-Version: 1.0 Can-Retransform-Classes: true Class-Path: dependency_lib/javassist-3.28.0-GA.jar Premain-Class: agent.MyAgent
(4) 替换编译后的class 文件
(5) 打包
D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./ 已添加清单 正在添加: agent/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: agent/MyAgent$1.class(输入 = 3142) (输出 = 1533)(压缩了 51%) 正在添加: agent/MyAgent.class(输入 = 941) (输出 = 524)(压缩了 44%) 正在添加: dependency_lib/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: dependency_lib/javassist-3.28.0-GA.jar(输入 = 851531) (输出 = 794719)(压缩了 6%) 正在忽略条目META-INF/ 正在忽略条目META-INF/MANIFEST.MF
(6) 编写测试类编译后测试:
package cn.qz; import java.util.concurrent.CountDownLatch; public class PlainTest { public static void main(String[] args) throws InterruptedException { new PlainTest().test(); CountDownLatch countDownLatch = new CountDownLatch(1); countDownLatch.await(); } public void test() throws InterruptedException { Thread.sleep(5 * 1000); System.out.println("cn.qz.PlainTest.test\t" + 111222); } }
1》测试:
D:\agentjar>java -javaagent:D:\agentjar\agent.jar cn.qz.PlainTest ====premain 方法执行 null sun.instrument.InstrumentationImpl@6979e8cb ====premain2 方法执行 ClassLoader : jdk.internal.loader.ClassLoaders$AppClassLoader@383534aa className : cn/qz/PlainTest cn.qz.PlainTest.test 111222 cn.qz.PlainTest.test() exec time is :5001ms
2》 使用arthas 实时反编译查看生成的类信息
/* * Decompiled with CFR. */ package cn.qz; import java.util.concurrent.CountDownLatch; public class PlainTest { /* * WARNING - void declaration */ public static void main(String[] stringArray) throws InterruptedException { void var2_1; void var4_2; long startTimeAgent = System.currentTimeMillis(); String methodNameAgent = "cn.qz.PlainTest.main(java.lang.String[])"; new PlainTest().test(); CountDownLatch countDownLatch = new CountDownLatch(1); /*10*/ countDownLatch.await(); Object var6_4 = null; System.out.println(new StringBuffer().append((String)var4_2).append(" exec time is :").append(System.currentTimeMillis() - var2_1).append("ms").toString()); } /* * WARNING - void declaration */ public void test() throws InterruptedException { void var1_1; void var3_2; long startTimeAgent = System.currentTimeMillis(); String methodNameAgent = "cn.qz.PlainTest.test()"; /*14*/ Thread.sleep(5000L); /*15*/ System.out.println("cn.qz.PlainTest.test\t111222"); Object var5_3 = null; System.out.println(new StringBuffer().append((String)var3_2).append(" exec time is :").append(System.currentTimeMillis() - var1_1).append("ms").toString()); } }
3. 使用asm实现方法查看执行时长
jdk 自身的包也封装了ASM相关。 下面的简单测试是基于jdk 自带的asm, 不需要引入其他的包。asm 是基于字节码的, 所以如果用asm 需要对字节码简单的了解。
1. 原生类查看字节码指令
(1) 类
package cn.qz; public class Client { public static void main(String[] args) { System.out.println(new Client().test1("3", "2")); } public String test1(String test1, String param2) { long l = System.currentTimeMillis(); // 代码逻辑 long l2 = System.currentTimeMillis() - l; System.out.println("The cost time of test1() is " + l2 + " ms"); return "123"; } }
(2) javap 查看相关字节码指令
D:\study\agentmvn\target\classes>javap -c cn.qz.Client Compiled from "Client.java" public class cn.qz.Client { public cn.qz.Client(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class cn/qz/Client 6: dup 7: invokespecial #4 // Method "<init>":()V 10: ldc #5 // String 3 12: ldc #6 // String 2 14: invokevirtual #7 // Method test1:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 17: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 20: return public java.lang.String test1(java.lang.String, java.lang.String); Code: 0: invokestatic #9 // Method java/lang/System.currentTimeMillis:()J 3: lstore_3 4: invokestatic #9 // Method java/lang/System.currentTimeMillis:()J 7: lload_3 8: lsub 9: lstore 5 11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 14: new #10 // class java/lang/StringBuilder 17: dup 18: invokespecial #11 // Method java/lang/StringBuilder."<init>":()V 21: ldc #12 // String The cost time of test1() is 23: invokevirtual #13 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 26: lload 5 28: invokevirtual #14 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 31: ldc #15 // String ms 33: invokevirtual #13 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: invokevirtual #16 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 39: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 42: ldc #17 // String 123 44: areturn }
2. agent.MyAgent 代理类
package agent; import java.lang.instrument.Instrumentation; public class MyAgent { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new ProfilingTransformer()); } }
3. agent.ProfilingTransformer
package agent; import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.ClassWriter; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; public class ProfilingTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { // 排除一些不需要处理的 if (!className.startsWith("cn/qz")) { return classfileBuffer; } return getBytes(loader, className, classfileBuffer); } catch (Throwable e) { e.printStackTrace(); } return classfileBuffer; } private byte[] getBytes(ClassLoader loader, String className, byte[] classfileBuffer) { ClassReader cr = new ClassReader(classfileBuffer); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); ClassVisitor cv = new ChangeVisitor(cw); cr.accept(cv, ClassReader.EXPAND_FRAMES); return cw.toByteArray(); } }
4. agent.ChangeVisitor
package agent; import jdk.internal.org.objectweb.asm.ClassVisitor; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import jdk.internal.org.objectweb.asm.Type; import jdk.internal.org.objectweb.asm.commons.AdviceAdapter; public class ChangeVisitor extends ClassVisitor { ChangeVisitor(ClassVisitor classVisitor) { super(Opcodes.ASM4, classVisitor); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions); if (name.equals("<init>")) { return methodVisitor; } return new ChangeAdapter(Opcodes.ASM4, methodVisitor, access, name, desc); } static class ChangeAdapter extends AdviceAdapter { private int startTimeId = -1; private String methodName = null; ChangeAdapter(int api, MethodVisitor mv, int access, String name, String desc) { super(api, mv, access, name, desc); methodName = name; } @Override protected void onMethodEnter() { super.onMethodEnter(); startTimeId = newLocal(Type.LONG_TYPE); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitIntInsn(LSTORE, startTimeId); } @Override protected void onMethodExit(int opcode) { super.onMethodExit(opcode); int durationId = newLocal(Type.LONG_TYPE); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false); mv.visitVarInsn(LLOAD, startTimeId); mv.visitInsn(LSUB); mv.visitVarInsn(LSTORE, durationId); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false); mv.visitLdcInsn("The cost time of " + methodName + "() is "); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitVarInsn(LLOAD, durationId); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false); mv.visitLdcInsn(" ms"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } } }
5. 测试类cn.qz.PlainTest
package cn.qz; public class PlainTest { public static void main(String[] args) throws InterruptedException { PlainTest apiTest = new PlainTest(); String res01 = apiTest.test1(111, 17); System.out.println(res01); Thread.sleep(500* 1000); } public String test1(int uId, int age) throws InterruptedException { Thread.sleep(5 * 1000); return "hello world!"; } }
6. 用-javaagent:D:/agentjar/agent.jar 代理后查看测试结果:
The cost time of test1() is 5000 ms
hello world!
7. arthas 反编译查看asm 字节码处理过的类:
/* * Decompiled with CFR. */ package cn.qz; public class PlainTest { public static void main(String[] stringArray) throws InterruptedException { long l = System.currentTimeMillis(); PlainTest apiTest = new PlainTest(); /* 8*/ String res01 = apiTest.test1(111, 17); /* 9*/ System.out.println(res01); /*10*/ Thread.sleep(500000L); /*11*/ long l2 = System.currentTimeMillis() - l; System.out.println("The cost time of main() is " + l2 + " ms"); } public String test1(int n, int n2) throws InterruptedException { long l = System.currentTimeMillis(); /*14*/ Thread.sleep(5000L); long l2 = System.currentTimeMillis() - l; System.out.println("The cost time of test1() is " + l2 + " ms"); return "hello world锛?"; } }
2. agentmain 的使用
Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。大体可以理解为JVM 启动了一个AttachListener, 可以接收一些参数然后做对应的处理。也被称为Attach 技术。
1. 简单使用
1. 编写agent.MyAgent
package agent; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; public class MyAgent { public static void agentmain(String agentOps, Instrumentation inst) throws UnmodifiableClassException { System.out.println("====agentmain 方法执行"); System.out.println(agentOps); System.out.println(inst); System.out.println("====agentmain 方法执行2"); // 通过Instrumentation 获取到加载的所有类的信息。 实际类型是: sun.instrument.InstrumentationImpl Class[] classes = inst.getAllLoadedClasses(); for (Class cls : classes) { if (cls.getName().startsWith("cn.qz")) { System.out.println("agent.MyAgent.agentmain\t" + cls.getName()); } } } }
2. 类似于上面premain 编写 META-INF/MANIFEST.MF
Manifest-Version: 1.0 Can-Retransform-Classes: true Agent-Class: agent.MyAgent
注意最后的空行
3. 生成jar 包
D:\agentjar>jar cvfm agent.jar ./META-INF/MANIFEST.MF ./ 已添加清单 正在添加: agent/(输入 = 0) (输出 = 0)(存储了 0%) 正在添加: agent/MyAgent.class(输入 = 1065) (输出 = 598)(压缩了 43%) 正在忽略条目META-INF/ 正在忽略条目META-INF/MANIFEST.MF
4. 编写一个正常运行的类
package cn.qz; public class PlainTest { public static void main(String[] args) throws InterruptedException { while (true) { Thread.sleep(5 * 1000); new PlainTest().test(); } } public void test() throws InterruptedException { // System.out.println("cn.qz.PlainTest.test\t" + 111222); } }
5. 用agentmain 获取上面JVM中所有的类
由于agent main方式无法向premain方式那样在命令行指定代理jar,因此需要借助Attach Tools API。 需要将 jdk/lib/tools.jar 引入到classpath 中, 然后获取到所有的JVM, 然后指定的JVM 执行指定的代理。
VirtualMachine.list() 可以达到类似于jps 的效果, 可以获取当前的所有的JVM 以及相关启动类和pid 信息。
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import java.util.List; public class AttachTest { public static void main(String[] args) throws Exception { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor virtualMachineDescriptor : list) { // 程序以cn.qz 开头 boolean b = virtualMachineDescriptor.displayName().startsWith("cn.qz"); if (b) { System.out.println(virtualMachineDescriptor.displayName() + "\t" + virtualMachineDescriptor.id()); VirtualMachine vm = VirtualMachine.attach(virtualMachineDescriptor.id()); vm.loadAgent("D:\\agentjar\\agent.jar"); break; } } } }
结果:
(1) AttachTest 相当于一个独立的进程,控制台如下:
cn.qz.PlainTest 11104 ======开始agentmain 方法====
(2) PlainTest 进程如下:
====agentmain 方法执行 null sun.instrument.InstrumentationImpl@629aa6f8 ====agentmain 方法执行2 agent.MyAgent.agentmain cn.qz.PlainTest
也可以类似于premain 在IDEA调试。我们通过vm.loadAgent 调的方法实际是在另一个指定的VM内部调用的, 查看调用链如下: (可以看到是在另一个监听线程里面为入口开始调用的)
2. agentmain 实现监测方法执行时间
1. 修改agent 类 (用javassit 对指定类增强)
package agent; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import java.lang.instrument.ClassDefinition; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; public class MyAgent { public static void agentmain(String agentOps, Instrumentation inst) throws UnmodifiableClassException { System.out.println("====agentmain 方法执行"); System.out.println(agentOps); System.out.println(inst); System.out.println("====agentmain 方法执行2"); // 通过Instrumentation 获取到加载的所有类的信息。 实际类型是: sun.instrument.InstrumentationImpl Class[] classes = inst.getAllLoadedClasses(); for (Class cls : classes) { if (cls.getName().startsWith("cn.qz")) { System.out.println("agent.MyAgent.agentmain\t" + cls.getName()); try { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get(cls.getName()); CtMethod[] declaredMethods = ctClass.getDeclaredMethods(); for (CtMethod method : declaredMethods) { // 修改方法体来实现, 增加两个局部变量用于记录执行时间 method.addLocalVariable("startTimeAgent", CtClass.longType); method.insertBefore("startTimeAgent = System.currentTimeMillis();"); method.addLocalVariable("methodNameAgent", cp.get(String.class.getName())); method.insertBefore("methodNameAgent = \"" + method.getLongName() + "\";"); method.insertAfter("System.out.println(methodNameAgent + \" exec time is :\" + (System.currentTimeMillis() - startTimeAgent) + \"ms\");"); } ClassDefinition classDefinition = new ClassDefinition(cls, ctClass.toBytecode()); inst.redefineClasses(classDefinition); } catch (Exception e) { e.printStackTrace(); } } } } }
2. 修改MANIFEST.MF
Manifest-Version: 1.0 Can-Retransform-Classes: true Can-Redefine-Classes: true Agent-Class: agent.MyAgent
注意最后有空格,Can-Redefine-Classes 是允许重新定义class。
3. 重新测试
(1) 查看控制台:
cn.qz.PlainTest.test() exec time is :0ms
cn.qz.PlainTest.test() exec time is :0ms
...
(2) 用arthas 反编译查看类
/* * Decompiled with CFR. */ package cn.qz; public class PlainTest { public static void main(String[] args) throws InterruptedException { String methodNameAgent = "cn.qz.PlainTest.main(java.lang.String[])"; long startTimeAgent = System.currentTimeMillis(); while (true) { /* 7*/ Thread.sleep(5000L); new PlainTest().test(); } } /* * WARNING - void declaration */ public void test() throws InterruptedException { void var1_2; void var3_1; String methodNameAgent = "cn.qz.PlainTest.test()"; long startTimeAgent = System.currentTimeMillis(); Object var5_3 = null; System.out.println(new StringBuffer().append((String)var3_1).append(" exec time is :").append(System.currentTimeMillis() - var1_2).append("ms").toString()); } }
补充: sun.instrument.InstrumentationImpl 核心类源码如下:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package sun.instrument; import java.lang.instrument.ClassDefinition; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.ProtectionDomain; import java.util.jar.JarFile; public class InstrumentationImpl implements Instrumentation { private final TransformerManager mTransformerManager = new TransformerManager(false); private TransformerManager mRetransfomableTransformerManager = null; private final long mNativeAgent; private final boolean mEnvironmentSupportsRedefineClasses; private volatile boolean mEnvironmentSupportsRetransformClassesKnown; private volatile boolean mEnvironmentSupportsRetransformClasses; private final boolean mEnvironmentSupportsNativeMethodPrefix; private InstrumentationImpl(long var1, boolean var3, boolean var4) { this.mNativeAgent = var1; this.mEnvironmentSupportsRedefineClasses = var3; this.mEnvironmentSupportsRetransformClassesKnown = false; this.mEnvironmentSupportsRetransformClasses = false; this.mEnvironmentSupportsNativeMethodPrefix = var4; } public void addTransformer(ClassFileTransformer var1) { this.addTransformer(var1, false); } public synchronized void addTransformer(ClassFileTransformer var1, boolean var2) { if (var1 == null) { throw new NullPointerException("null passed as 'transformer' in addTransformer"); } else { if (var2) { if (!this.isRetransformClassesSupported()) { throw new UnsupportedOperationException("adding retransformable transformers is not supported in this environment"); } if (this.mRetransfomableTransformerManager == null) { this.mRetransfomableTransformerManager = new TransformerManager(true); } this.mRetransfomableTransformerManager.addTransformer(var1); if (this.mRetransfomableTransformerManager.getTransformerCount() == 1) { this.setHasRetransformableTransformers(this.mNativeAgent, true); } } else { this.mTransformerManager.addTransformer(var1); } } } public synchronized boolean removeTransformer(ClassFileTransformer var1) { if (var1 == null) { throw new NullPointerException("null passed as 'transformer' in removeTransformer"); } else { TransformerManager var2 = this.findTransformerManager(var1); if (var2 != null) { var2.removeTransformer(var1); if (var2.isRetransformable() && var2.getTransformerCount() == 0) { this.setHasRetransformableTransformers(this.mNativeAgent, false); } return true; } else { return false; } } } public boolean isModifiableClass(Class<?> var1) { if (var1 == null) { throw new NullPointerException("null passed as 'theClass' in isModifiableClass"); } else { return this.isModifiableClass0(this.mNativeAgent, var1); } } public boolean isRetransformClassesSupported() { if (!this.mEnvironmentSupportsRetransformClassesKnown) { this.mEnvironmentSupportsRetransformClasses = this.isRetransformClassesSupported0(this.mNativeAgent); this.mEnvironmentSupportsRetransformClassesKnown = true; } return this.mEnvironmentSupportsRetransformClasses; } public void retransformClasses(Class<?>... var1) { if (!this.isRetransformClassesSupported()) { throw new UnsupportedOperationException("retransformClasses is not supported in this environment"); } else { this.retransformClasses0(this.mNativeAgent, var1); } } public boolean isRedefineClassesSupported() { return this.mEnvironmentSupportsRedefineClasses; } public void redefineClasses(ClassDefinition... var1) throws ClassNotFoundException { if (!this.isRedefineClassesSupported()) { throw new UnsupportedOperationException("redefineClasses is not supported in this environment"); } else if (var1 == null) { throw new NullPointerException("null passed as 'definitions' in redefineClasses"); } else { for(int var2 = 0; var2 < var1.length; ++var2) { if (var1[var2] == null) { throw new NullPointerException("element of 'definitions' is null in redefineClasses"); } } if (var1.length != 0) { this.redefineClasses0(this.mNativeAgent, var1); } } } public Class[] getAllLoadedClasses() { return this.getAllLoadedClasses0(this.mNativeAgent); } public Class[] getInitiatedClasses(ClassLoader var1) { return this.getInitiatedClasses0(this.mNativeAgent, var1); } public long getObjectSize(Object var1) { if (var1 == null) { throw new NullPointerException("null passed as 'objectToSize' in getObjectSize"); } else { return this.getObjectSize0(this.mNativeAgent, var1); } } public void appendToBootstrapClassLoaderSearch(JarFile var1) { this.appendToClassLoaderSearch0(this.mNativeAgent, var1.getName(), true); } public void appendToSystemClassLoaderSearch(JarFile var1) { this.appendToClassLoaderSearch0(this.mNativeAgent, var1.getName(), false); } public boolean isNativeMethodPrefixSupported() { return this.mEnvironmentSupportsNativeMethodPrefix; } public synchronized void setNativeMethodPrefix(ClassFileTransformer var1, String var2) { if (!this.isNativeMethodPrefixSupported()) { throw new UnsupportedOperationException("setNativeMethodPrefix is not supported in this environment"); } else if (var1 == null) { throw new NullPointerException("null passed as 'transformer' in setNativeMethodPrefix"); } else { TransformerManager var3 = this.findTransformerManager(var1); if (var3 == null) { throw new IllegalArgumentException("transformer not registered in setNativeMethodPrefix"); } else { var3.setNativeMethodPrefix(var1, var2); String[] var4 = var3.getNativeMethodPrefixes(); this.setNativeMethodPrefixes(this.mNativeAgent, var4, var3.isRetransformable()); } } } private TransformerManager findTransformerManager(ClassFileTransformer var1) { if (this.mTransformerManager.includesTransformer(var1)) { return this.mTransformerManager; } else { return this.mRetransfomableTransformerManager != null && this.mRetransfomableTransformerManager.includesTransformer(var1) ? this.mRetransfomableTransformerManager : null; } } private native boolean isModifiableClass0(long var1, Class<?> var3); private native boolean isRetransformClassesSupported0(long var1); private native void setHasRetransformableTransformers(long var1, boolean var3); private native void retransformClasses0(long var1, Class<?>[] var3); private native void redefineClasses0(long var1, ClassDefinition[] var3) throws ClassNotFoundException; private native Class[] getAllLoadedClasses0(long var1); private native Class[] getInitiatedClasses0(long var1, ClassLoader var3); private native long getObjectSize0(long var1, Object var3); private native void appendToClassLoaderSearch0(long var1, String var3, boolean var4); private native void setNativeMethodPrefixes(long var1, String[] var3, boolean var4); private static void setAccessible(final AccessibleObject var0, final boolean var1) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { var0.setAccessible(var1); return null; } }); } private void loadClassAndStartAgent(String var1, String var2, String var3) throws Throwable { ClassLoader var4 = ClassLoader.getSystemClassLoader(); Class var5 = var4.loadClass(var1); Method var6 = null; NoSuchMethodException var7 = null; boolean var8 = false; try { var6 = var5.getDeclaredMethod(var2, String.class, Instrumentation.class); var8 = true; } catch (NoSuchMethodException var13) { var7 = var13; } if (var6 == null) { try { var6 = var5.getDeclaredMethod(var2, String.class); } catch (NoSuchMethodException var12) { } } if (var6 == null) { try { var6 = var5.getMethod(var2, String.class, Instrumentation.class); var8 = true; } catch (NoSuchMethodException var11) { } } if (var6 == null) { try { var6 = var5.getMethod(var2, String.class); } catch (NoSuchMethodException var10) { throw var7; } } setAccessible(var6, true); if (var8) { var6.invoke((Object)null, var3, this); } else { var6.invoke((Object)null, var3); } setAccessible(var6, false); } private void loadClassAndCallPremain(String var1, String var2) throws Throwable { this.loadClassAndStartAgent(var1, "premain", var2); } private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable { this.loadClassAndStartAgent(var1, "agentmain", var2); } private byte[] transform(ClassLoader var1, String var2, Class<?> var3, ProtectionDomain var4, byte[] var5, boolean var6) { TransformerManager var7 = var6 ? this.mRetransfomableTransformerManager : this.mTransformerManager; return var7 == null ? null : var7.transform(var1, var2, var3, var4, var5); } static { System.loadLibrary("instrument"); } }
可以理解为其内部的方法都是JVM 发起调用的。
关于javassit和asm 使用参考: https://www.cnblogs.com/qlqwjy/p/15216085.html