It has been 733 days since the last update, the content of the article may be outdated.
概念
Instrument
- Instrument 是 JVM 提供的一个可以修改已加载类的类库,依赖于 JVMTI 的 Attach API 机制
- 要使用 Instrument 的类修改功能,需要实现
java.lang.instrument.ClassFileTransformer
接口
- 可以在
ClassFileTransformer#transform
中利用 ASM 或者 Byte Buddy 等工具对字节码进行操作
- Instrument 通过与 Java Agent 结合来注入到 JVM 中
JVMTI & Agent
- JPDA(Java Platform Debugger Architecture)是 JDK 标准,必须实现
- 如果 JVM 启动时开启了 JPDA,那么类是允许被重新加载的
- 已加载的旧版类信息被卸载,然后重新加载新版本的类
- JVMTI 是 JVM 提供的一套对 JVM 进行操作的工具接口,Agent 是 JVMTI 的一种实现
- Attach API 的作用:提供 JVM 进程间通信的能力
- Attach 后,目标 JVM 在运行时走到 Agent 中定义的
agentmain
方法
Modules
1 2 3 4 5 6
| $ tree -L 1 . ├── app ├── dynamic-agent-loader ├── dynamic-load-agent └── static-load-agent
|
App
pom.xml1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
|
1 2 3 4 5 6
| @SpringBootApplication public class AppApplication { public static void main(String[] args) { SpringApplication.run(AppApplication.class, args); } }
|
1 2
| $ mvn clean package spring-boot:repackage $ java -jar target/app-0.0.1-SNAPSHOT.jar
|
Static Load
1 2 3 4 5 6 7 8 9 10
| public class PremainAgent {
public static void premain(String preArgs, Instrumentation instrumentation) { System.out.println("premain start"); System.out.println("preArgs: " + preArgs); Arrays.stream(instrumentation.getAllLoadedClasses()) .findFirst() .ifPresent(klass -> System.out.println("premain loadedClass: " + klass.getName())); } }
|
pom.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Premain-Class>me.zhongmingmao.staticloadagent.PremainAgent</Premain-Class> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| $ mvn clean package
$ jar -tf target/static-load-agent-0.0.1-SNAPSHOT.jar META-INF/ META-INF/MANIFEST.MF me/ me/zhongmingmao/ me/zhongmingmao/staticloadagent/ META-INF/maven/ META-INF/maven/me.zhongmingmao/ META-INF/maven/me.zhongmingmao/static-load-agent/ me/zhongmingmao/staticloadagent/PremainAgent.class META-INF/maven/me.zhongmingmao/static-load-agent/pom.xml META-INF/maven/me.zhongmingmao/static-load-agent/pom.properties
$ unzip -p target/static-load-agent-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF | less Manifest-Version: 1.0 Premain-Class: me.zhongmingmao.staticloadagent.PremainAgent Build-Jdk-Spec: 1.8 Created-By: Maven JAR Plugin 3.2.2
$ java -javaagent:/xxx/static-load-agent-0.0.1-SNAPSHOT.jar=static_load -jar target/app-0.0.1-SNAPSHOT.jar premain start preArgs: static_load premain loadedClass: me.zhongmingmao.staticloadagent.PremainAgent
|
Dynamic Load
Dynamic Agent
1 2 3 4 5 6 7 8 9 10
| public class AgentMain {
public static void agentmain(String args, Instrumentation instrumentation) { System.out.println("agentmain start"); System.out.println("args: " + args); Arrays.stream(instrumentation.getAllLoadedClasses()) .findFirst() .ifPresent(klass -> System.out.println("agentmain loadedClass: " + klass.getName())); } }
|
pom.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.2</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Agent-Class>me.zhongmingmao.dynamicloadagent.AgentMain</Agent-Class> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| $ mvn clean package
$ jar -tf target/dynamic-load-agent-0.0.1-SNAPSHOT.jar META-INF/ META-INF/MANIFEST.MF me/ me/zhongmingmao/ me/zhongmingmao/dynamicloadagent/ META-INF/maven/ META-INF/maven/me.zhongmingmao/ META-INF/maven/me.zhongmingmao/dynamic-load-agent/ me/zhongmingmao/dynamicloadagent/AgentMain.class META-INF/maven/me.zhongmingmao/dynamic-load-agent/pom.xml META-INF/maven/me.zhongmingmao/dynamic-load-agent/pom.properties
$ unzip -p target/dynamic-load-agent-0.0.1-SNAPSHOT.jar META-INF/MANIFEST.MF | less Manifest-Version: 1.0 Agent-Class: me.zhongmingmao.dynamicloadagent.AgentMain Build-Jdk-Spec: 1.8 Created-By: Maven JAR Plugin 3.2.2
|
Agent Loader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @SpringBootApplication public class DynamicAgentLoaderApplication implements CommandLineRunner {
public static void main(String[] args) { SpringApplication.run(DynamicAgentLoaderApplication.class, args); }
@Override public void run(String... args) { if (args.length < 3) { return; } String process = args[0]; String agent = args[1]; String options = args[2];
VirtualMachine.list().stream() .filter(descriptor -> descriptor.displayName().contains(process)) .findAny() .ifPresent( descriptor -> { try { VirtualMachine vm = VirtualMachine.attach(descriptor.id()); vm.loadAgent(agent, options); vm.detach(); } catch (AgentLoadException | AgentInitializationException | AttachNotSupportedException | IOException e) { e.printStackTrace(); } }); } }
|
pom.xml1 2 3 4 5 6 7
| <dependency> <groupId>com.sun</groupId> <artifactId>tools</artifactId> <version>1.8.0</version> <scope>system</scope> <systemPath>${JAVA_HOME}/lib/tools.jar</systemPath> </dependency>
|
1 2 3 4 5 6 7
| $ mvn clean package spring-boot:repackage
$ java -Xbootclasspath/a:${JAVA_HOME}/lib/tools.jar -jar target/dynamic-agent-loader-0.0.1-SNAPSHOT.jar AppApplication /xxx/dynamic-load-agent-0.0.1-SNAPSHOT.jar dynamic_load
agentmain start args: dynamic_load agentmain loadedClass: me.zhongmingmao.dynamicagentloader.DynamicAgentLoaderApplication$$Lambda$292/1136497418
|