Instrumentation.addTransformer 是挂载字节码转换逻辑的唯一入口,必须在 premain 或 agentmain 中调用,且对已加载类重转换需显式传 true 参数并配合 retransformClasses。

Instrumentation.addTransformer 是挂载字节码转换逻辑的唯一入口,它把 ClassFileTransformer 实例注册进 JVM 的类加载链路中,让后续类定义(defineClass)或重转换(retransformClasses)时自动触发 transform 方法。
这个操作不是“可选步骤”,而是必须前置完成的动作——否则你的字节码修改逻辑永远不会被执行。
必须在 premain 或 agentmain 中调用
JVM 只允许在两个时机注册 transformer:
- premain 阶段:JVM 启动初期、应用 main 方法执行前。这是最常用、最稳妥的方式,能覆盖绝大多数业务类。
-
agentmain 阶段:JVM 运行中动态加载 agent,此时需显式调用
addTransformer(transformer, true)并立即配合retransformClasses(...),否则已加载类不会被处理。
注意:
addTransformer(transformer)默认注册的是 不可重转换 转换器;若要对已加载类生效,必须用addTransformer(transformer, true),且确保目标类支持重转换(非 Bootstrap 加载的核心类,如String、ArrayList不行)。
挂载后的行为逻辑
一旦注册成功,每次发生以下任一动作,JVM 就会调用你 transform 方法:
- 新类首次被
ClassLoader.defineClass加载(包括热部署、反射加载等) - 显式调用
Instrumentation.retransformClasses(Class...) - (仅限
canRetransform = true的转换器)重转换已加载类
transform 方法签名如下:
byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer
) throws IllegalClassFormatException;-
className是斜杠分隔格式(如"com/example/Service"),不是点号格式; -
classfileBuffer是原始字节码,禁止直接修改,必须拷贝后修改并返回新数组; - 若不想处理该类,直接返回
null即可,不影响后续流程。
常见挂载错误与规避方式
❌ 在
transform方法里才去注册另一个 transformer
→ 不生效。addTransformer必须在premain/agentmain中完成,不能延迟到 transform 内部。❌ 忘记传
true参数导致retransformClasses无反应
→ 已加载类不会触发transform,除非注册时明确声明支持重转换。❌ 对
java.*或sun.*等 Bootstrap 类调用retransformClasses
→ 抛UnsupportedOperationException,这些类无法重转换。❌ 多次注册同一 transformer 实例却未 remove
→ 可能重复增强,比如方法耗时统计被插两遍,造成嵌套计时。
建议做法:
- 使用
Set<ClassFileTransformer>管理注册状态,避免重复 add; - 在 agent 生命周期结束前(如 shutdown hook)调用
removeTransformer清理; - 判断
classBeingRedefined != null来区分是首次加载还是重转换,便于差异化处理。
挂载后不等于万事大吉
注册只是第一步。真正起作用还依赖:
- 类名过滤是否准确(比如用
className.startsWith("com.myapp.")); - 字节码修改是否合法(局部变量表、栈帧、异常表需同步更新,否则类校验失败);
- 是否避开 JDK 内建优化(如
invokedynamic引导方法、Lambda 生成类需特殊处理)。
ASM 和 Byte Buddy 都能帮你绕过部分细节,但底层规则不变:JVM 只认合规字节码,不认你的意图。
不复杂但容易忽略

















