从JDK1.5之后引入了java angent技术
Java Agent 是一种强大的技术,它允许开发者在 JVM 启动时或运行期间动态地修改类的字节码,从而实现诸如性能监控、日志记录、AOP(面向切面编程)等功能
java agent依赖于Instrumentation API,是 Java 提供的一个用于操作已加载类的字节码的标准API。通过这个API,你可以修改类的字节码,甚至可以在不重启应用的情况下重新定义已经加载的类
具体的实现过程是通过一个agent来对字节码进行修改,agent实际上是一个特殊的jar包,包含了一个或多个特定的方法(premain
或 agentmain
),这些方法会在JVM启动时或者运行期间被调用
premain:
顾名思义是在应用程序的main函数执行之前执行的
1.在 premain 方法执行时,JVM 已经加载了一些基础类,但你的应用程序的主要类(以及其它未加载的类)通常还没有被加载。
2.通过 premain 方法中注册的 ClassFileTransformer 可以拦截并修改之后加载的所有类的字节码。
3.如果你希望通过 Java Agent 修改某个类的行为,确保该类在 premain 方法中已经注册了相应的转换器是很重要的。对于已经加载的类,你需要使用 retransformClasses 或其他方法来重新处理它们。
对于这种方式实现的agent需要在启动应用程序的时候通过参数指定执行这个agenr的jar包
agentmain:
需要通过attach API通过 Attach API 动态加载 Agent,允许你对已经加载的类进行重新转换(retransform)
对于这种方式实现的agent可以在运行时通过Attach API 动态执行
主要注意的时动态执行agent的时候,如果原应用程序中的方法已经在执行的话,正在执行的方法会按照旧的字节码信息继续执行,但是agent执行之后的方法执行的是根据agent修改之后新的字节码信息
新旧共存的原理:
类加载之后方法的字节码是存在方法区中的,agent修改字节码之后原来旧的字节码将会被覆盖,那么新旧是怎么共存的呢?
实际上jvm中,每加载一个类就会对应生成一个klass对象来表示这个类的元数据结构
方法区中会存储已被虚拟机加载的类信息、常量、静态变量等。也就是方法区中的类信息指向这个klass对象
当字节码被修改之后,方法区中类信息会更新为最新的klass对象,即方法区中存储的都是最新的类信息
在方法调用执行的时候,会创建一个栈帧,栈帧并不直接指向方法区中的字节码,而是依赖于类的 Klass
对象提供的元数据来执行字节码
所以即使修改了类的字节码,只是新建了一个klass对象,并且方法区中指向了这个新的klass对象,但是栈帧并不知道方法区中的变化,因为栈帧只是在创建的时候绑定了一个klass对象,并且根据这个klass对象来执行上下文
那两种agent方法可以共存吗?
是可以共存的,但是不会在同一 JVM 实例中同时执行。
通过参数启动的话只会执行premain方法内的逻辑,通过attach API的话只会执行agentmain方法中的逻辑