Java Agent

Java Agent是jdk1.5以后引入的,也叫做Java代理。

javaAgent是运行方法之前的拦截器。我们利用javaAgent和ASM字节码技术,在JVM加载class二进制文件的时候,利用ASM动态的修改加载的class文件,在监控的方法前后添加计时器功能,用于计算监控方法耗时,同时将方法耗时及内部调用情况放入处理器,处理器利用栈先进后出的特点对方法调用先后顺序做处理,当一个请求处理结束后,将耗时方法轨迹和入参map输出到文件中,然后根据map中相应参数或耗时方法轨迹中的关键代码区分出我们要抓取的耗时业务。最后将相应耗时轨迹文件取下来,转化为xml格式并进行解析,通过浏览器将代码分层结构展示出来,方便耗时分析.

Java Agent是运行在main方法之前的拦截器,它内定的方法名叫premain,也就是说要先执行premain再执行main方法。

利用Java Agent和ASM字节码结束可以在JVM加载class文件的时候,利用ASM动态的修改已经加载的class文件。

Java Agent探针工具特点:

1、支持方法执行耗时范围抓取,根据耗时范围抓取
2、抓取特定的代码配置
3、支持入口方法参数输出功能
4、提供web页面接口耗时、代码调用关系

Java Agent支持目标JVM启动时加载,也支持在目标JVM运行时加载,这两种不同的加载模式会使用不同的入口函数,如果需要在目标JVM启动的同时加载Agent,那么可以选择实现下面的方法

public static void premain(String agentArgs, Instrumentation inst); 
public static void premain(String agentArgs);

JVM将首先寻找[1],如果没有发现[1],再寻找[2]。

启动时修改主要是在jvm启动时,执行native函数的Agent_OnLoad方法,在方法执行时,执行如下步骤:

• 创建InstrumentationImpl对象
• 监听ClassFileLoadHook事件
•调用InstrumentationImpl的loadClassAndCallPremain方法,在这个方法里会去调用javaagent里MANIFEST.MF里指定的Premain-Class类的premain方法

JVMTI全称JVM Tool Interface,是JVM暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,也就是JVM每执行到一定的逻辑时就会调用一些事件的回调接口(如果回调接口存在),这些接口就可以被开发者扩展自己的逻辑。

JPLISAgent:全称为Java programming Language Instrumatation Service Agent。
在这里插入图片描述

如果希望在目标JVM运行时加载Agent,则需要实现下面的方法:

public static void agentmain(String agentArgs, Instrumentation inst); 
public static void agentmain(String agentArgs);

运行时修改主要是通过jvm的attach机制来请求目标jvm加载对应的agent,执行native函数的Agent_OnAttach方法,在方法执行时,执行如下步骤:

• 创建InstrumentationImpl对象
• 监听ClassFileLoadHook事件
•调用InstrumentationImpl的loadClassAndCallAgentmain方法,在这个方法里会去调用javaagent里MANIFEST.MF里指定的Agentmain-Class类的agentmain方法

在这里插入图片描述

这两组方法的第一个参数AgentArgs是随同 “–javaagent”一起传入的程序参数,如果这个字符串代表了多个参数,就需要自己解析这些参数。inst是Instrumentation类型的对象,是JVM自动传入的,我们可以拿这个参数进行类增强等操作(可以用于获取JVM信息。)。

/**
 * @author chenTom
 *
 *
 */
public class AgentTest {
    /**
     * 在类加载前执行Agent时首先调用Instrumentation中的addTransformer方法进行注册,
     * 注册参数为ClassFileTransformer对象,然后实现ClassFileTransformer中的transform方法,
     * 在该方法中对要加载的类的字节码做一些处理。
     * @param agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst){
        System.out.println("==========================进入premain方法==========================");
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                //todo logic
                return new byte[0];
            }
        });
    }
}
public interface ClassFileTransformer {
    byte[]
    transform(  ClassLoader         loader,
                String              className,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)
        throws IllegalClassFormatException;
}

ASM

asm是字节码增强技术,通过asm可以生成新的class文件,也可以动态的修改即将要装载入jvm的类信息。

ASM是一个Java字节码操控框架,它被用来动态生成类或者增强已有类的功能。

ASM可以直接生产二进制class文件,也可以在类被加载到Java虚拟机之前动态改变类。Java类存储在.class文件中,ASM就从这些类文件中读入信息,然后可以动态改变类行为、分析类行为、或者生成新类。

ASM字节码增强技术主要是用反射的时候提升性能,如果单纯用jdk的反射调用,性能是比较低的,而使用字节码增强技术后反射的调用时间已经基本可以与直接调用相当了。

反射为什么性能低:

  • 1、Class.forname()调用本地方法,比较耗时
  • 2、Class.getMethod会遍历类的方法。

ASM框架的核心类有以下几个:

ClassReader:该类用来解析编译过的class字节码
ClassWriter:该类用来重新构建编译后的类,比如修改类名,属性以及方法,甚至可以生成新的类的字节码文件。
ClassAdapter:该类实现了ClassVisitor接口,它将对它的方法调用委托给另一个ClassVisitor对象。

因此可以将ASM理解为对类文件的CRUD,经过CRUD的字节码可以转换为类。

ASM的解析类似于SAX解析XML文件,相比于其它方式比如CGLIB,它的优势在于性能更高,在Spring中都是使用的cglib动态代理,而cglib本身就是使用ASM。

利用ASM可以获得class文件的详细信息:比如类名、父类名、接口、成员名、方法参数名、局部变量名、元数据等。
还可以对class文件进行动态修改,比如增加、删除、修改某个类的方法。

CGLIB是对ASM的封装,简化ASM的操作,降低ASM的使用门槛。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐