zoukankan      html  css  js  c++  java
  • 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

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    HDU2303(数论)大整数求余+素数筛选
    2015 多校联赛 ——HDU5360(贪心+优先队列)
    2015 多校联赛 ——HDU5363(快速幂)
    2015 多校联赛 ——HDU5353(构造)
    2015 多校联赛 ——HDU5348(搜索)
    2015 多校联赛 ——HDU5350(huffman)
    hibernate投影查询
    Hibernate中Criteria的完整用法
    mysql 常用查询语句记录
    ssh整合步骤整理
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/15639710.html
Copyright © 2011-2022 走看看