Java - agent探针
Java - agent探针
介绍
使用 Instrumentation,使得开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了 一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
JVM启动前静态Instrument
Instrumentation 的最大作用,就是类定义动态改变和操作。在 Java SE 5 及其后续版本当中,开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 -javaagent参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。
编写premain方法
-
定义一个java类,实现如下两个方法当中的一个:
public static void premain(String agentArgs, Instrumentation inst); [1] public static void premain(String agentArgs); [2]
其中,[1] 的优先级比 [2] 高,将会被优先执行([1] 和 [2] 同时存在时,[2] 被忽略)。在这个 premain 函数中,开发者可以进行对类的各种操作。
- agentArgs 是 premain 函数得到的程序参数,随同 “-javaagent”一起传入。与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。
- Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。
-
打包jar
将这个java类打包成一个jar文件,并在其中的META-INF/MAINFEST.MF属性当中加入”Premain-Class“来指定步骤1中编写的带有premain的类。maven打包方式可以参考配置如下<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.2</version> <configuration> <!--<archive>--> <!--<index>true</index>--> <!--<manifestFile>--> <!--src/main/resources/META-INF/MANIFEST.MF--> <!--</manifestFile>--> <!--<manifest>--> <!--<addDefaultImplementationEntries/>--> <!--</manifest>--> <!--</archive>--> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <!-- 参数方式启动agent需要这个 --> <Premain-Class> ${permain} </Premain-Class> <!-- 启动后附加启动agent需要这个 --> <Agent-Class> com.demo.XXX </Agent-Class> <!-- 是否可以重新转换类 --> <Can-Retransform-Classes> true </Can-Retransform-Classes> <!-- 是否可以重新定义类 --> <Can-Redefine-Classes> true </Can-Redefine-Classes> </manifestEntries> </archive> </configuration> </plugin>
-
运行
用如下方式运行带有Instrumenttation的java程序java -javaagent:jar路径 [= 传入 premain 的参数 ] 其他参数
JVM启动后动态Instrument
在java6之前,开发者只能在程序main函数执行前,启动Instrumentation,java6之后提供了一个与premain类似的agentmain方法,该方法可以在程序main函数运行之后在执行。写法与premain函数类似。
public static void agentmain (String agentArgs, Instrumentation inst); [1]
public static void agentmain (String agentArgs);[2]
同样,[1] 的优先级比 [2] 高,将会被优先执行([1] 和 [2] 同时存在时,[2] 被忽略)。跟 premain 函数一样,开发者可以在 agentmain 中进行对类的各种操作。其中的 agentArgs 和 Inst 的用法跟 premain 相同。
打包方法与premain类类似,唯一区别是MANIFEST文件的Agent-Class替换Premain-Class。
启动方式与premain类也不相同,agentmain实现类可以通过Attach API来触发。
String pid = "34534";// 目标JVM进程ID
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
virtualMachine.loadAgent(jar);
virtualMachine.detach();
System.out.println("load success");
Instrument核心API
- ClassFileTransformer:定义了类加载前的预处理类,可以在这个类中对要加载的类的字节码做一些处理,譬如进行字节码增强;
- Instrumentation:增强器,由JVM在入口参数中传递给我们,提供了如下的功能:
- addTransformer/removeTransformer:注册/删除ClassFileTransformer;
- retransformClasses:对于已经加载的类重新进行转换处理,即会触发重新加载类定义,需要注意的是,新加载的类不能修改旧有的类声明,譬如不能增加属性、不能修改方法声明;
- redefineClasses:与如上类似,但不是重新进行转换处理,而是直接把处理结果(bytecode)直接给JVM;
- getAllLoadedClasses:获得当前已经加载的Class,可配合retransformClasses使用;
- getInitiatedClasses:获得由某个特定的ClassLoader加载的类定义;
- getObjectSize:获得一个对象占用的空间,包括其引用的对象;
- appendToBootstrapClassLoaderSearch/appendToSystemClassLoaderSearch:增加BootstrapClassLoader/SystemClassLoader的搜索路径;
- isNativeMethodPrefixSupported/setNativeMethodPrefix:判断JVM是否支持拦截Native Method;
热部署,其实就是动态或者说运行时修改类,大的方向说有2种方式:
- 使用 agentmain,不需要重新创建类加载器,可直接修改类,但是有很多限制。
- 使用 premain 可以在类第一次加载之前修改,加载之后修改需要重新创建类加载器。或者 在自定义的类加载器种修改,但这种方式比较耦合。
无论是哪种,都需要字节码修改的库,比如ASM,javassist ,cglib 等,很多。总之,通过java.lang.instrument 包配合字节码库,可以很方便的动态修改类,或者进行热部署。