共计 2308 个字符,预计需要花费 6 分钟才能阅读完成。
为什么需要学习 Java Agent
Java Agent 技术是 JVM 生态中一块隐藏的宝藏。工作中第一次接触到这个概念,是因为团队需要无侵入地监控应用性能。传统方式要修改大量代码,而 Agent 只需要在启动时加一个参数,这种优雅的解决方案让我眼前一亮。但当时资料零散,走了不少弯路,现在把系统化的学习路径分享给大家。

一、核心概念:先理解再动手
-
本质是什么
Java Agent 本质上是一个 JAR 包,通过 JVMTI 接口与 JVM 交互。它像是一个潜伏在 JVM 内部的观察员,能拦截类加载过程。我们团队用它实现了方法耗时统计,无需业务代码埋点。 -
两种加载方式
- 静态加载:通过
-javaagent参数在启动时载入 -
动态加载:通过 Attach API 在运行时挂载(JDK5+ 支持)
生产环境更推荐静态方式,稳定性更高。 -
能力边界
- 修改字节码(但不能修改已有类的结构)
- 获取 JVM 信息(线程 / 内存等)
- 不能做的事情:修改 final 类、增加父类等
二、循序渐进的学习路线
阶段 1:基础准备(1- 2 周)
- JVM 基础补强
- 类加载机制(双亲委派模型)
- 字节码文件结构(建议用 javap 反编译.class 文件观察)
-
推荐书籍:《深入理解 Java 虚拟机》前 4 章
-
工具链熟悉
- JDK 工具:javac、javap、jar
- 字节码工具:ASM/Javassist(后者更适合新手)
- 调试技巧:使用
-XX:+TraceClassLoading观察加载过程
阶段 2:字节码操作实战(2- 3 周)
通过简单的代码修改来建立信心:
// 使用 Javassist 在方法前后插入日志
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.example.Service");
CtMethod m = cc.getDeclaredMethod("process");
m.insertBefore("System.out.println(System.currentTimeMillis());");
m.insertAfter("System.out.println(System.currentTimeMillis());");
byte[] byteCode = cc.toBytecode();
阶段 3:完整 Agent 开发(1 周)
下面是一个统计方法耗时的完整示例:
// 1. 定义 Premain-Class
public class MyAgent {public static void premain(String args, Instrumentation inst) {inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {if (!className.replace("/", ".").startsWith("com.yourpackage")) {return null; // 只处理特定包}
// 使用 ASM 修改字节码
ClassReader reader = new ClassReader(classfileBuffer);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor = new TimeCostVisitor(writer);
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();}
});
}
}
需要在 MANIFEST.MF 中声明:
Premain-Class: com.agent.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
三、避坑指南(血泪经验)
- 类加载隔离
- Agent 的类加载器是 SystemClassLoader
- 遇到 NoClassDefFoundError 时,考虑把依赖包放入 Agent JAR
-
推荐使用 Maven-Shade 插件打包
-
性能影响
- 批量转换类时会显著增加启动时间
-
解决方案:
- 使用
ClassFileTransformer的过滤条件 - 延迟加载非关键类
- 使用
-
调试技巧
- 使用
-Djdk.attach.allowAttachSelf=true允许自我调试 - Arthas 的 retransform 命令可以实时验证修改
四、生产级实践建议
- 监控场景
- 方法耗时统计(注意 native 方法无法拦截)
-
异常采集(结合 MDC 实现链路追踪)
-
AOP 增强
- 动态配置开关(通过
-javaagent:agent.jar=debug=true传参) -
禁止修改核心库类(如 java.lang 包)
-
发布流程
- 灰度发布:先对少量节点加载 Agent
- 回滚方案:准备好移除 Agent 的应急预案
学习资源推荐
- 视频教程:B 站《Java Agent 全解析》
- 开源项目:
- SkyWalking 的 Java 探针
- Arthas 的字节码增强模块
- 动手实验:尝试给 Spring Controller 自动添加请求日志
刚开始接触 Agent 时,被各种字节码操作吓退过。但当我第一次成功拦截到方法调用时,那种成就感至今难忘。建议从小的实验开始,比如先给所有 toString()方法加个日志,慢慢建立信心。这项技术虽然门槛高,但掌握后能解决很多传统方式难以处理的问题,值得投入时间学习。
正文完
