本文目录导读:

我来为你详细介绍Java探针(Java Agent)的完整案例,包括原理、实现和实际应用。
Java探针基础概念
1 什么是Java探针
Java探针(Java Agent)是JDK 1.5引入的特性,可以在JVM加载类时进行字节码增强,实现方法监控、性能分析、故障诊断等功能。
2 核心原理
- Premain方式:虚拟机启动时加载,通过java -agentlib参数指定
- Agentmain方式:虚拟机运行中动态加载,通过Attach机制
完整实现案例
1 项目结构
java-agent-demo
├── pom.xml
├── src
│ └── main
│ └── java
│ ├── com/demo/agent/
│ │ ├── AgentMain.java
│ │ ├── PremainMain.java
│ │ └── transformer/
│ │ └── MethodCostTransformer.java
│ └── com/demo/test/
│ └── TestMain.java
2 Maven配置
<?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>com.demo</groupId>
<artifactId>java-agent-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- ASM框架用于字节码操作 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>9.2</version>
</dependency>
<!-- Attach API -->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
3 核心探针代码
AgentPremain.java
package com.demo.agent;
import com.demo.agent.transformer.MethodCostTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.List;
/**
* Java探针启动入口(premain方式)
*/
public class AgentPremain {
/**
* JVM启动时加载agent,会调用此方法
*
* @param agentArgs agent参数
* @param inst Instrumentation实例
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("========== Java Agent启动(premain) ==========");
System.out.println("Agent参数: " + agentArgs);
System.out.println("JVM启动参数: " + getJvmArgs());
// 添加字节码转换器
inst.addTransformer(new MethodCostTransformer(), true);
System.out.println("Agent初始化完成,开始监控方法耗时...");
}
/**
* 获取当前Java进程的主要信息
*/
private static String getJvmArgs() {
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
List<String> inputArgs = runtimeMXBean.getInputArguments();
return String.join(" ", inputArgs);
}
}
AgentMain.java(动态Attach方式)
package com.demo.agent;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.lang.instrument.Instrumentation;
import java.util.List;
/**
* Java探针(agentmain方式 - 动态加载)
*/
public class AgentMain {
/**
* 动态加载agent时会调用此方法
*/
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("========== Java Agent动态加载(agentmain) ==========");
System.out.println("Agent参数: " + agentArgs);
// 添加字节码转换器
inst.addTransformer(new MethodCostTransformer(), true);
System.out.println("Agent动态加载完成");
}
/**
* 主动加载到指定进程的工具方法
*/
public static void attachToProcess(String pid, String agentPath) {
try {
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentPath, "start");
vm.detach();
System.out.println("成功将Agent加载到进程: " + pid);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 列出当前所有Java进程
*/
public static void listJavaProcesses() {
List<VirtualMachineDescriptor> list = VirtualMachine.list();
System.out.println("当前Java进程列表:");
for (VirtualMachineDescriptor vmd : list) {
System.out.println(" PID: " + vmd.id() + ", 名称: " + vmd.displayName());
}
}
}
MethodCostTransformer.java
package com.demo.agent.transformer;
import org.objectweb.asm.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
/**
* 方法耗时统计的字节码转换器
* 使用ASM框架进行字节码增强
*/
public class MethodCostTransformer implements ClassFileTransformer {
// 需要监控的包名前缀
private static final String MONITOR_PACKAGE = "com/demo/test/";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
// 只监控指定包下的类
if (className == null || !className.startsWith(MONITOR_PACKAGE)) {
return null;
}
try {
System.out.println("开始增强类: " + className.replace('/', '.'));
// 使用ASM读取原始字节码
ClassReader classReader = new ClassReader(classfileBuffer);
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
// 创建访问器
ClassVisitor classVisitor = new MethodCostClassVisitor(classWriter, className);
// 执行转换
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
System.out.println("增强完成: " + className.replace('/', '.'));
return classWriter.toByteArray();
} catch (Exception e) {
System.err.println("增强类失败: " + className + ", 错误: " + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
* 类访问器
*/
private static class MethodCostClassVisitor extends ClassVisitor {
private final String className;
public MethodCostClassVisitor(ClassVisitor cv, String className) {
super(Opcodes.ASM9, cv);
this.className = className;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
// 排除构造方法和静态初始化块
if (!name.equals("<init>") && !name.equals("<clinit>")) {
return new MethodCostVisitor(mv, className, name, descriptor);
}
return mv;
}
}
/**
* 方法访问器 - 核心增强逻辑
*/
private static class MethodCostVisitor extends MethodVisitor {
private final String className;
private final String methodName;
private final String methodDesc;
// 用于存储时间戳的变量索引
private int timestampLocal;
// 是否已增加局部变量
private boolean localAdded = false;
public MethodCostVisitor(MethodVisitor mv, String className, String methodName, String methodDesc) {
super(Opcodes.ASM9, mv);
this.className = className;
this.methodName = methodName;
this.methodDesc = methodDesc;
}
@Override
public void visitCode() {
super.visitCode();
if (!localAdded) {
// 在局部变量表中增加一个long类型的变量用于存储时间戳
timestampLocal = 0; // 需要根据实际情况获取
localAdded = true;
}
// 方法入口处插入代码
// long startTime = System.currentTimeMillis();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, timestampLocal);
System.out.println(" 已添加方法入口监控: " + methodName);
}
@Override
public void visitInsn(int opcode) {
// 在方法返回指令前插入时间计算代码
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
// long endTime = System.currentTimeMillis();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J", false);
mv.visitVarInsn(Opcodes.LSTORE, timestampLocal + 2);
// long cost = endTime - startTime
mv.visitVarInsn(Opcodes.LLOAD, timestampLocal + 2);
mv.visitVarInsn(Opcodes.LLOAD, timestampLocal);
mv.visitInsn(Opcodes.LSUB);
mv.visitVarInsn(Opcodes.LSTORE, timestampLocal + 4);
// System.out.println("方法名: " + cost + "ms")
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitLdcInsn(className.replace('/', '.') + "." + methodName + " 耗时: ");
mv.visitVarInsn(Opcodes.LLOAD, timestampLocal + 4);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String",
"valueOf", "(J)Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V", false);
System.out.println(" 已添加方法出口监控: " + methodName);
}
super.visitInsn(opcode);
}
}
}
4 测试代码
TestMain.java
package com.demo.test;
import com.demo.agent.AgentMain;
import java.util.concurrent.TimeUnit;
/**
* 测试Java探针的目标类
*/
public class TestMain {
public static void main(String[] args) throws Exception {
System.out.println("===== 启动测试程序 =====");
TestService service = new TestService();
for (int i = 0; i < 10; i++) {
service.processOrder("订单-" + i);
Thread.sleep(1000);
}
System.out.println("===== 测试程序结束 =====");
}
}
class TestService {
public void processOrder(String orderId) {
System.out.println("处理订单: " + orderId);
// 模拟业务逻辑
validateOrder(orderId);
updateInventory(orderId);
notifyUser(orderId);
}
private void validateOrder(String orderId) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void updateInventory(String orderId) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void notifyUser(String orderId) {
try {
Thread.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
5 MANIFEST.MF配置
创建 src/main/resources/META-INF/MANIFEST.MF:
Manifest-Version: 1.0 Premain-Class: com.demo.agent.AgentPremain Agent-Class: com.demo.agent.AgentMain Can-Redefine-Classes: true Can-Retransform-Classes: true Permit-Redefinition-Classes: true
6 使用说明
启动时加载(premain)
# 打包 mvn clean package # 运行测试程序(不加载Agent) java -cp target/java-agent-demo-1.0-SNAPSHOT.jar com.demo.test.TestMain # 运行测试程序(加载Agent) java -javaagent:target/java-agent-demo-1.0-SNAPSHOT.jar -cp target/java-agent-demo-1.0-SNAPSHOT.jar com.demo.test.TestMain
动态加载(agentmain)
创建一个动态加载的触发类:
package com.demo.test;
import com.demo.agent.AgentMain;
public class AttachTest {
public static void main(String[] args) {
// 列出所有Java进程
AgentMain.listJavaProcesses();
// 指定目标进程PID
String targetPid = "12345";
String agentPath = "/path/to/agent.jar";
// 动态加载
AgentMain.attachToProcess(targetPid, agentPath);
}
}
实际运行效果
加载Agent后,控制台输出:
========== Java Agent启动(premain) ==========
Agent参数: null
JVM启动参数: -javaagent:target/java-agent-demo-1.0-SNAPSHOT.jar
Agent初始化完成,开始监控方法耗时...
开始增强类: com.demo.test.TestMain
增强完成: com.demo.test.TestMain
开始增强类: com.demo.test.TestService
增强完成: com.demo.test.TestService
===== 启动测试程序 =====
处理订单: 订单-0
com.demo.test.TestService.validateOrder 耗时: 102ms
com.demo.test.TestService.updateInventory 耗时: 201ms
com.demo.test.TestService.notifyUser 耗时: 153ms
com.demo.test.TestService.processOrder 耗时: 461ms
...
进阶功能扩展
1 加入耗时统计存储
public class MethodCostCollector {
private static final Map<String, List<Long>> METHOD_COSTS = new ConcurrentHashMap<>();
public static void recordMethodCost(String methodName, long cost) {
METHOD_COSTS.computeIfAbsent(methodName, k -> new ArrayList<>()).add(cost);
}
public static void printStats() {
METHOD_COSTS.forEach((method, costs) -> {
DoubleSummaryStatistics stats = costs.stream()
.mapToDouble(Long::doubleValue)
.summaryStatistics();
System.out.println(String.format(
"%s: 次数=%d, 平均=%.2fms, 最大=%.2fms, 最小=%.2fms",
method, stats.getCount(), stats.getAverage(),
stats.getMax(), stats.getMin()
));
});
}
}
2 支持参数配置
public class AgentConfig {
private static Properties properties = new Properties();
public static void load(String configPath) {
try (InputStream is = new FileInputStream(configPath)) {
properties.load(is);
} catch (Exception e) {
e.printStackTrace();
}
}
public static boolean shouldMonitor(String className) {
String monitorPackage = properties.getProperty("monitor.package");
return className != null && className.startsWith(monitorPackage);
}
public static boolean isEnableMonitor(String methodName) {
String excludeMethods = properties.getProperty("exclude.methods", "");
return !Arrays.asList(excludeMethods.split(",")).contains(methodName);
}
}
注意事项
- 性能开销:字节码增强会带来一定性能影响,建议只监控关键方法
- 兼容性:注意ASM版本与Java版本兼容性
- 异常处理:增强代码需要完善的异常处理,避免影响业务逻辑
- 类加载问题:注意处理被多个ClassLoader加载的类
- 安全考虑:生产环境使用需经过严格测试
实际应用场景
- APM监控:统计方法耗时、请求链路追踪
- 性能分析:定位慢方法、内存泄漏诊断
- 日志增强:自动记录方法入参、返回值
- 热部署:动态修改类行为而不重启应用
- 安全审计:记录敏感操作调用信息
这个案例展示了Java探针的完整实现,你可以根据实际需求进行扩展和修改。