共计 1620 个字符,预计需要花费 5 分钟才能阅读完成。
为什么需要 Java Agent
传统 APM 工具和日志采集方案通常需要在代码中显式埋点,这不仅增加了开发工作量,还可能因为遗漏导致监控不全面。而 Java Agent 通过 Instrumentation API 实现了无侵入式的监控,能够在类加载时动态修改字节码,完美解决了这些问题。

主流字节码操作框架对比
- ASM:性能最高但 API 最底层,适合对性能要求极高的场景
- Javassist:API 最简单但运行时性能较差,适合快速原型开发
- Byte Buddy:平衡了易用性和性能,是大多数场景的最佳选择
核心实现步骤
-
创建 Premain-Class
public class MyAgent {public static void premain(String args, Instrumentation inst) {inst.addTransformer(new MyTransformer()); } } -
实现 ClassFileTransformer
public class MyTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { try { // 使用 Byte Buddy 修改字节码 return new ByteBuddy() .redefine(classBeingRedefined, ClassFileLocator.Simple.of(className, classfileBuffer)) .method(ElementMatchers.any()) .intercept(MethodDelegation.to(MyInterceptor.class)) .make() .getBytes();} catch (Exception e) { // 必须捕获所有异常 return null; // 返回 null 表示不修改字节码 } } }
生产环境最佳实践
- 类加载器隔离 :
- 为 Agent 创建独立的 ClassLoader
-
避免污染应用类加载器
-
避免死锁 :
- 不要在 transform 方法中加载新类
-
使用双重检查锁时特别小心
-
异步处理 :
private static final ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(() -> {// 耗时操作});
性能优化建议
通过 JMH 测试,不同框架对方法执行的影响:
| 框架 | 基准耗时 (ns/op) |
|---|---|
| 原始代码 | 15.2 |
| ASM | 16.8 |
| Byte Buddy | 17.3 |
| Javassist | 24.6 |
代码规范示例
/**
* 拦截器实现
* @param obj 目标对象
* @param method 被拦截方法
* @param args 方法参数
* @return 方法执行结果
*/
@NonNull
public static Object intercept(@NonNull Object obj, @NonNull Method method,
@NonNull Object[] args) throws Exception {try (Timer timer = new Timer()) {return method.invoke(obj, args);
}
}
思考题
- 如何在不重启 JVM 的情况下更新 Agent 逻辑?
- 当 Agent 需要监控 Spring Bean 时,需要注意哪些类加载问题?
- 当 Agent 自身出现异常时,应该如何设计降级策略?
总结
Java Agent 开发虽然入门门槛较高,但掌握核心原理后,可以极大提升应用的监控能力。建议从简单的字节码修改开始,逐步深入理解 ClassLoader 机制和 Instrumentation API。在生产环境中使用时,务必做好性能测试和异常处理。
正文完
