# 6.1 Java Agent 基础
# 6.1.1 实现一个简单的Java Agent
Agent的代码如下:
package org.example;
import java.lang.instrument.Instrumentation;
public class Agent {
/**
* 以vm参数的方式载入,在Java程序的main方法执行之前执行
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain run");
}
/**
* 以Attach的方式载入,在Java程序启动后执行
*/
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("agentmain run");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
我们需要在agentmain或者premain方法中实现具体的Agent逻辑,这里是你大显身手的地方, 如读取JVM的各种数据,修改类的字节码,只要你能想到的,一般都可以实现。
因为Java Agent的特殊性,还需要一些特殊的配置,在META-INF目录下创建MANIFEST.MF文件,
这部分可以手动生成也可以使用maven插件自动生成,这里建议使用maven插件自动生成。
在pom.xml文件中添加如下插件配置,其中Premain-Class
和Agent-Class
的配置值为上面Agent类的全限定名称。
配置如下:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>org.example.Agent</Premain-Class>
<Agent-Class>org.example.Agent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
解压jar包查看META-IN/MANIFEST.MF文件,Java Agent 的入口类org.example.Agent
已经被写入到文件中。
Manifest-Version: 1.0
Premain-Class: org.example.Agent
Archiver-Version: Plexus Archiver
Built-By: jrasp
Agent-Class: org.example.Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_261
2
3
4
5
6
7
8
9
10
11
12
# 6.1.2 加载Agent
前面说了,一个Java Agent既可以在程序运行前加载,也可以在程序运行后加载,两者有什么区别呢?
- 命令行方式启动
-javaagent:/path/to/your/jarpath[=options]
参数是可选择的,如jacoco的启动参数如下:
java -javaagent:jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=localhost,append=true -jar application.jar
premain方法允许有如下2种方法签名:
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);
2
当以上两种方式都存在时,带有Instrumentation参数的方法的优先级更高,会被JVM优先调用。
这里以命令行形式在SpringBoot应用中加载一个Java Agent。
java -javaagent:/path/to/your/my-agent-1.0.jar -jar springboot.jar
可以看到"premain run"日志输出。
- 运行时加载
应用程序启动之后,通过某种特定的手段加载Java Agent,这个特定的手段就是VirtualMachine的 attach api, 这个api其实是JVM进程之间的的沟通桥梁,底层通过socket进行通信,JVM A可以发送一些指令给JVM B,B收到指令之后, 可以执行对应的逻辑,比如在命令行中经常使用的jstack、jcmd、jps等,很多都是基于这种机制实现的。 在前面的章节实现了attach,这里不再重复。
# 6.1.3 Agent的功能开关
以下是agent jar文件的Manifest Attributes:
- Premain-Class
如果JVM启动时指定了代理,那么此属性指定代理类,即包含premain方法的类。 如果JVM启动时指定了代理,那么此属性是必需的。 如果该属性不存在,那么JVM将中止。注:此属性是类名,不是文件名或路径。
Agent-Class 如果实现支持JVM启动之后某一时刻启动代理的机制,那么此属性指定代理类。
即包含 agentmain 方法的类。 此属性是必需的,如果不存在,代理将无法启动。
注:这是类名,而不是文件名或路径。
Boot-Class-Path 由引导类加载器搜索的路径列表。路径表示目录或库(在许多平台上通常作为 JAR 或 zip 库被引用)。
查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。此属性是可选的。
Can-Redefine-Classes 布尔值(true 或 false,与大小写无关)。是否能重定义此代理所需的类。
true以外的值均被视为false。此属性是可选的,默认值为false。
Can-Retransform-Classes 布尔值(true 或 false,与大小写无关)。是否能重转换此代理所需的类。
true以外的值均被视为false。此属性是可选的,默认值为 false。
Can-Set-Native-Method-Prefix 布尔值(true 或 false,与大小写无关)。是否能设置此代理所需的本机方法前缀。
true 以外的值均被视为 false。此属性是可选的,默认值为 false。允许当前agent给native方法设置前缀,可以间接实现native字节码的修改
一个Agent jar 包中可以同时存在Premain-Class和Agent-Class,当 javaagent以命令行方式启动,仅使用Premain-Class,而忽略Agent-Class, 以运行时启动javaagent,则相反。
上面的6个属性在Java Agent中都有使用。可以参考官方文档:
https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/compact3-package-summary.html