Java探针技术Instrumentation

3,098 阅读3分钟

Instrumentation简介

Java探针技术,通过Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在JVM上的程序,甚至能够替换和修改某些类的定义而对业务代码没有侵入

成熟应用

java领域的APM(Application Performance Management应用性能管理)工具,几乎都是基于Instrumentation实现的。

  • zipkin:Twitter公司开源的一个分布式追踪工具,被Spring Cloud Sleuth集成,使用广泛而稳定
  • skywalking:中国人吴晟(华为)开源的一款分布式追踪,分析,告警的工具,现在是Apache旗下开源项目
  • cat:大众点评开源的一款分布式链路追踪工具。

Arthas 是Alibaba开源的Java诊断工具也是基于此。

这么多优秀的产品都在使用java探针Instrumentation,是不是有兴趣深入了解一下呢。

使用流程

入口代理类

在main方法前执行

public class MyAgent {

    static Instrumentation instrumentation;

    public static void premain(String agentArgs, Instrumentation inst)
            throws Exception {
        try {
            instrumentation = inst;
            inst.addTransformer(new MyClassFileTransformer());
        } catch (Exception e) {
        }
    }

}

需要提供一下两个方法之一,如果两个的话,前一个优先级较高

public static void premain(String agentOps, Instrumentation instrumentation);
public static void premain(String agentOps);

自定义ClassFileTransformer

package top.soft1010.java.myagent;

import javassist.*;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import javassist.scopedpool.ScopedClassPool;
import javassist.scopedpool.ScopedClassPoolRepositoryImpl;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
 * Created by bjzhangjifu on 2021/9/27.
 */
public class MyClassFileTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("Transforming " + className);
        //非自有类, 直接返回
        if (!className.startsWith("top/soft1010")) {
            return classfileBuffer;
        }
        //为null,表示由bootstrapLoader加载的,不能重写
        if (loader == null) {
            return classfileBuffer;
        }
        try {
//            if (className != null && className.indexOf("/") != -1) {
//                className = className.replaceAll("/", ".");
//            }
            CtClass ctClass = ClassPool.getDefault().makeClass(new java.io.ByteArrayInputStream(classfileBuffer));
            CtMethod[] methods = ctClass.getDeclaredMethods();
            for (CtMethod method : methods) {
                try {
                    method.insertAfter("System.out.println("--after--");");
                    method.insertBefore("System.out.println("--before--");");
                } catch (CannotCompileException e) {
                    e.printStackTrace();
                }
            }
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classfileBuffer;
    }
}

ClassFileTransformer用于修改class文件,该操作发生在 JVM 加载 class 之前。它只有一个transform方法,实现该方法可以修改 class字节码(这里可以使用javassist,asm等),并返回修改后的 class字节码,有两点要注意:

  1. 如果此方法返回null, 表示我们不对类进行处理直接返回。否则,会用我们返回的byte[]来代替原来的类
  2. ClassFileTransformer必须添加进Instrumentation才能生效 Instrumentation#addTransformer(ClassFileTransformer)
  3. 当存在多个Transformer时,一个Transformer调用返回的byte数组将成为下一个Transformer调用的输入

运行

这里我们直接写一个main方法类,当然也可以是jar

public class MyTest {
    public static void main(String[] args) {
        new MyTest().test();
    }

    private void test() {
        System.out.println(" ----- test ----- ");
    }
}

执行命令

java -javaagent:myagent.jar top.soft1010.java.myagent.MyTest

面试

1、加载类的时候,对字节码进行修改?

能,使用java探针,javaagent就可以。
JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。
JavaAgent 是运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法。
premain方法有个Instrumentation 参数,通过它我们可以添加自定义一个FileTransformer,这个接口只有一个方法transform 通过这个方法我们能获取加载类的字节码,当然也可以修改编辑字节码,比如在指定类的方法前后添加监控信息等。

2、说说java探针技术javaagent的应用

skywalking:中国人吴晟(华为)开源的一款分布式追踪,分析,告警的工具,现在是Apache旗下开源项目
cat:大众点评开源的一款分布式链路追踪工具。
arthas: 阿里开源的java诊断工具
当然好多大公司内部也有好多自己定制的apm工具也是基于此

源码

文中涉及到的代码 源码