本文目录导读:

我来为你详细介绍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探针的完整实现,你可以根据实际需求进行扩展和修改。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播