javaagent介绍及使用

Javaagent是从JDK1.5开始引入的,agent的意思是代理,它实际上是个包含premain方法的jar包。要被监控的程序执行时需要在java启动参数中增加-javaagent参数,指定要加载的javaagent包。premain方法先于main方法执行,可以在类加载之前修改类定义,从而实现很多功能。

当Javaagent正确被加载,premain方法会自动被调用,并且会获取到Instrumentation对象的一个引用。通过Instrumentation这个对象,可以实现很多功能,比如修改类路径、获取已经加载的类、获取对象大小等等。其中最常用的是通过addTransformer方法添加类转化器,动态修改类定义,从而实现无侵入的应用监控。

从JDK1.6开始,提供了agentmain方法,提供了动态修改运行中的已经被加载的类的途径。一般通过VirtualMachine的attach(pid)方法获得VirtualMachine实例,随后调用loadagent方法将Javaagent的jar包加载到目标JVM中。

总结:Javaagent 只要作用在class被加载之前对其加载,插入我们需要添加的字节码。

实现Agent启动方法

  1. 启动时加载,实现permain方法

    1
    2
    [1]public static void premain(String agentArgs, Instrumentation instrumentation);
    [2]public static void premain(String agentArgs);
  2. 运行时加载,实现agentmain方法

    1
    2
    3
    4
    5
    6
    [1]public static void agentmain(String agentArgs, Instrumentation instrumentation);
    [2]public static void agentmain(String agentArgs);

    // 运行时调用方法,找到进程Id 挂载到JVM抽象
    VirtualMachine vm = VirtualMachine.attach("{pid}");
    vm.loadAgent("{agent}", "{params}");

    上面的两个方法只需要实现一个即可,且[1]的优先级是高于[2]的,即如果上面的两个方法同时出现,则只会执行[1]方法

使用示例

创建maven工程

Java Agent 是以 jar 包的形式存在。创建一个maven工程。引入插件自动生成MANIFEST.MF文件,替换成自己的项目中的AgentApp类路径:我的是xyz.xcyd.AgentApp
这样就能在打包的时候 将配置自动写入MANIFEST.MF文件

  • javaassist包来修改字节码
  • jmockit包VirtualMachine进行动态挂载Agent

完整 pom.xml 配置如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>javaagent</groupId>
<artifactId>xyz.xcyd</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.33</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<!-- 修改为自己的类路径 -->
<Premain-Class>xyz.xcyd.AgentApp</Premain-Class>
<Agent-Class>xyz.xcyd.AgentApp</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>

</project>

编写 AgentApp 类 和 MyTransformer类实现ClassFileTransformer接口,完成耗时统计方法

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
36
package xyz.xcyd;

import java.lang.instrument.Instrumentation;

public class AgentApp {

/**
* jvm 参数形式启动,运行此方法
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst){
System.out.println("Enter premain ......");
showExecTime(inst);
}

/**
* 动态 attach 方式启动,运行此方法
* @param agentArgs
* @param inst
*/
public static void agentmain(String agentArgs, Instrumentation inst){
System.out.println("Enter agentmain ......");
showExecTime(inst);
}

/**
* 显示执行时间
* @param inst
*/
private static void showExecTime(Instrumentation inst){
inst.addTransformer(new MyTransformer(), true);
}

}

实现自定义ClassFileTransformer

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
36
37
38
public class MyTransformer implements ClassFileTransformer {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

// 这里我们限制下,只针对目标包下进行耗时统计
if (!className.startsWith("xyz/xcyd")) {
return classfileBuffer;
}
System.out.println("正在加载类:" + className);

try {
ClassPool classPool = ClassPool.getDefault();
CtClass cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));

// 所有方法,统计耗时
for (CtMethod method : cl.getDeclaredMethods()) {
//需要通过`addLocalVariable`来声明局部变量
method.addLocalVariable("start", CtClass.longType);
//插入 开始语句
method.insertBefore("start = System.currentTimeMillis();");
String methodName = method.getLongName();
//创建并插入 打印语句 System.out.println("方法:test, 执行时间:" + (System.currentTimeMillis() - start));
String statement = String.format("System.out.println(\"方法:%s, 执行时间:\" + (System.currentTimeMillis() - start));", methodName);
method.insertAfter(statement);

}

byte[] transformed = cl.toBytecode();
return transformed;
} catch (Exception e) {
e.printStackTrace();
}

return classfileBuffer;
}

}

打包AgentApp,加入项目启动参数

将Agent项目打包成jar包

1
mvn install

找一个自己的SpringBoot项目,在Idea的 Run/Debug Configurations中快速配置 -javaagent:E:\IdeaWorkSpace\test-java-agent\target\xyz.xcyd-1.0-SNAPSHOT.jar Agent Jar包的路径
6tq7d0.md.png

启动项目 输出

1
方法:XXXXXXX$$EnhancerBySpringCGLIB$$ee06503b.login(java.lang.String), 执行时间:611