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

 

posted @ 2021-12-04 23:00  QiaoZhi  阅读(1720)  评论(0编辑  收藏  举报