专栏名称: 芋道源码
纯 Java 源码分享公众号,目前有「Dubbo」「SpringCloud」「Java 并发」「RocketMQ」「Sharding-JDBC」「MyCAT」「Elastic-Job」「SkyWalking」「Spring」等等
目录
相关文章推荐
芋道源码  ·  手把手教你实现一个Java Agent ·  14 小时前  
ImportNew  ·  亚马逊程序员破防:AI ... ·  2 天前  
芋道源码  ·  面试官:为什么数据库连接很消耗资源? ·  2 天前  
芋道源码  ·  2W字全面剖析 Mybatis 中的9种设计模式 ·  2 天前  
51好读  ›  专栏  ›  芋道源码

手把手教你实现一个Java Agent

芋道源码  · 公众号  · Java  · 2025-06-14 19:30

主要观点总结

文章介绍了「芋道快速开发平台」知识星球及其提供的部分资料,如《项目实战(视频)》、《互联网高频面试题》等,并提及了一个开源项目,具有多项功能。同时,文章还探讨了Java Agent技术、Instrumentation接口以及ClassFileTransformer的使用,以及Arthas的trace功能,用于无侵入式地解决代码性能优化问题。最后,文章鼓励读者加入知识星球,以获取更多的技术提升。

关键观点总结

关键观点1: 「芋道快速开发平台」知识星球及其提供的资料

「芋道快速开发平台」知识星球提供了一系列有用的技术资料,如《项目实战(视频)》、《互联网高频面试题》等,帮助读者提升技术能力。

关键观点2: 开源项目及其功能

文章提到的开源项目具有多项功能,如RBAC权限、SaaS多租户、数据权限等,并提供了相应的技术文档和源码。

关键观点3: Java Agent技术与Instrumentation接口

Java Agent技术通过Instrumentation接口及其ClassFileTransformer实现无侵入式地解决代码性能优化问题。

关键观点4: Arthas的trace功能

Arthas的trace功能通过字节码增强技术,无侵入式地跟踪和统计方法耗时,用于性能优化。

关键观点5: 加入知识星球

文章鼓励读者加入「芋道快速开发平台」知识星球,以获取更多技术提升和资料。


正文

请到「今天看啥」查看全文


static void premain (String agentArgs, Instrumentation instrumentation) {
}

从字面上理解,就是运行在main()函数之前的类。在Java虚拟机启动时,在执行main()函数之前,会先运行指定类的premain()方法,在premain()方法中对class文件进行修改,它有两个入参

  1. agentArgs:启动参数,在JVM启动时指定
  2. instrumentation:上文所将的Instrumentation的实例,我们可以在方法中调用上文所讲的方法,注册对应的Class转换器,对Class文件进行修改

如下图,借助Instrumentation,JVM启动时的处理流程是这样的:JVM会执行指定类的premain()方法,在premain()中可以调用Instrumentation对象的addTransformer方法注册ClassFileTransformer。当JVM加载类时会将类文件的字节数组传递给ClassFileTransformer的transform方法,在transform方法中对Class文件进行解析和修改,之后JVM就会加载转换后的Class文件

JVM启动时的处理流程

那我们需要做的就是写一个转换Class文件的ClassFileTransformer,下面用一个计算函数耗时的小例子看看Java Agent是怎么使用的

public class MyClassFileTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        if ("com/example/aop/agent/MyTest".equals(className)) {
            // 使用ASM框架进行字节码转换
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new TimeStatisticsVisitor(Opcodes.ASM7, cw);
            cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
        return classfileBuffer;

    }
}
public class TimeStatisticsVisitor extends ClassVisitor {

    public TimeStatisticsVisitor(int api, ClassVisitor classVisitor) {
        super(Opcodes.ASM7, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
        if (name.equals("")) {
            return mv;
        }
        return new TimeStatisticsAdapter(api, mv, access, name, descriptor);
    }
}
public class TimeStatisticsAdapter extends AdviceAdapter {

    protected TimeStatisticsAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
        super(api, methodVisitor, access, name, descriptor);
    }

    @Override
    protected void onMethodEnter() {
        // 进入函数时调用TimeStatistics的静态方法start
        super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/aop/agent/TimeStatistics""start""()V"false);
        super.onMethodEnter();
    }

    @Override
    protected void onMethodExit(int opcode) {
        // 退出函数时调用TimeStatistics的静态方法end
        super.onMethodExit(opcode);
        super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/aop/agent/TimeStatistics""end""()V"false);
    }
}

public class TimeStatistics {
    public static ThreadLocal t = new ThreadLocal<>();

    public static void start() {
        t.set(System.currentTimeMillis());
    }
    public static void end() {
        long time = System.currentTimeMillis() - t.get();
        System.out.println(Thread.currentThread().getStackTrace()[2] + " spend: " + time);
    }
}
public class AgentMain {
    // premain()函数中注册MyClassFileTransformer转换器
    public static void premain (String agentArgs, Instrumentation instrumentation) {
        System.out.println("premain方法");
        instrumentation.addTransformer(new MyClassFileTransformer(), true);
    }
}

 
   
      org.apache.maven.plugins
      maven-assembly-plugin
      3.1.1
     
       
         
          jar-with-dependencies
       
       
         
            // 指定premain()的所在方法
            com.example.aop.agent.AgentMain
            com.example.aop.agent.AgentMain
            true
            true
         
       
     
     
       
          package
         
            single
         
       
     
   
   
      org.apache.maven.plugins
      maven-compiler-plugin
      3.1
     
        ${maven.compiler.source}
        ${maven.compiler.target}
     
   
 

使用命令行执行下面的测试类

java -javaagent:/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.example.aop.agent.MyTest
public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(3000);
    }
}

计算出了某个方法的耗时

计算出某个方法的耗时

Attach

在上面的例子中,我们只能在JVM启动时指定一个Agent,这种方式局限在main()方法执行前,如果我们想在项目启动后随时随地地修改Class文件,要怎么办呢?这个时候需要借助Java Agent的另外一个方法,该方法的签名如下

public static void agentmain (String agentArgs, Instrumentation inst) {
}

agentmain()的参数与premain()有着同样的含义,但是agentmain()是在Java Agent被Attach到Java虚拟机上时执行的,当Java Agent被attach到Java虚拟机上,Java程序的main()函数一般已经启动,并且程序很可能已经运行了相当长的时间,此时通过Instrumentation.retransformClasses()方法,可以动态转换Class文件并使之生效,下面用一个小例子演示一下这个功能

下面的类启动后,会不断打印出100这个数字,我们通过Attach功能使之打印出50这个数字

public class PrintNumTest {
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            System.out.println(getNum());
            Thread.sleep(3000);
        }
    }
    private static int getNum() {
        return 100;
    }
}

依然是定义一个ClassFileTransformer,使用ASM框架修改getNum()方法

public class PrintNumTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if ("com/example/aop/agent/PrintNumTest".equals(className)) {
            System.out.println("asm");
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new TransformPrintNumVisitor(Opcodes.ASM7, cw);
            cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
        return classfileBuffer;
    }
}
public class






请到「今天看啥」查看全文