Java agent技术
1. Javaagent定义
-
Javaagent是Java虚拟机(JVM)提供的一种机制,允许在运行时修改、增强或监控Java应用程序的行为
- Java agent 是一种特殊的Java程序(Jar文件),它是 Instrumentation 的客户端。与普通 Java 程序通过main方法启动不同,agent 并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过 Instrumentation API 与虚拟机交互。
3. Java agent 有两个启动时机,一个是在程序启动时通过 -javaagent 参数启动代理程序,一个是在程序运行期间通过 Java Tool API 中的 attach api 动态启动代理程序。
- Java agent 是一种特殊的Java程序(Jar文件),它是 Instrumentation 的客户端。与普通 Java 程序通过main方法启动不同,agent 并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过 Instrumentation API 与虚拟机交互。
2. JVM启动时静态加载
对于VM启动时加载的 agent,Instrumentation 会通过 premain 方法传入代理程序,premain 方法会在程序 main 方法执行之前被调用。此时大部分Java类都没有被加载(“大部分”是因为,agent类本身和它依赖的类还是无法避免的会先加载的),是一个对类加载埋点做手脚(addTransformer)的好机会。但这种方式有很大的局限性,Instrumentation 仅限于 main 函数执行前,此时有很多类还没有被加载,如果想为其注入 Instrumentation 就无法办到。
public static void premain(String args, Instrumentation inst) {
}
3. JVM 启动后动态加载
对于VM启动后动态加载的 agent,Instrumentation 会通过 agentmain 方法传入代理程序,agentmain 在 main 函数开始运行后才被调用。
public static void agentmain(String args, Instrumentation inst) {
}
-
agent连接目标程序的两种方式
- jvm参数启动(premain只能以这种方式启动)
- attach接入
jvm参数启动
-javaagent:C:\\Users\\kafka.xin\\Desktop\\agent\\target\\agent-1.0-SNAPSHOT-jar-with-dependencies.jar
attach接入
本地调试可以通过下面代码获取pid,linux可用相关命令查询
ManagementFactory.getRuntimeMXBean().getName().split("@")[0]
attach的方式接入(依赖tools.jar)
VirtualMachine vm = VirtualMachine.attach("17164"); vm.loadAgent("path/agent.jar"); vm.detach();
5. agent程序打包成jar
- 使用maven插件打包
- 通过MANIFEST.MF文件指定
使用maven插件打包
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>com.agent.AgentMainTraceAgent</Premain-Class>
<Agent-Class>com.agent.AgentMainTraceAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>
6. 重新加载类
可以使用Instrumentation::redefineClasses方法重新加载类,入参为ClassDefinition对象,伪代码如下:
//获取字节码
byte[] bytes = loadClassByPath(sysPath);
//获取已加载的类
Class[] classes = inst.getAllLoadedClasses();
//根据已加载的类找到旧的目标类
Class oldClass = findOldClass(classes);
//重新加载类
inst.redefineClasses(new ClassDefinition(oldClass, bytes));