instrumentation 是 java 提供的用于在类加载后、首次使用前重写字节码的机制,支持热补丁(如修复方法逻辑),但不可增删字段/方法或修改签名,需通过 -javaagent 启用,且受 jvm 严格限制。

Instrumentation 是什么,它能做什么
Java 的 Instrumentation 接口不是用来“加载新类”的,而是让你在类被 JVM 加载后、首次使用前,**重写其字节码**。它不改变类路径,也不影响类加载器层级,只对已加载或即将加载的类生效。这意味着:你不能用它新增一个 Spring Bean,但可以替换某个已有 Service 类里的 calculate() 方法逻辑——只要这个类还没被初始化(即没执行 <clinit></clinit> 或首次调用)。
必须启用 -javaagent 才能拿到 Instrumentation 实例
没有 -javaagent,Instrumentation 就是空引用。JVM 启动时必须显式挂载 agent jar,并通过 premain() 或 agentmain() 获取实例。生产环境通常用 agentmain() 动态 attach,避免重启:
- agent jar 必须包含
META-INF/MANIFEST.MF,声明Premain-Class和/或Agent-Class - attach 需要目标 JVM 开启
com.sun.management.jmxremote,且进程有权限(Linux 下常因/proc/pid/cmdline权限失败) - OpenJDK 11+ 默认禁用 attach:需额外加
-Djdk.attach.allowAttachSelf=true或确保使用tools.jar兼容版本
retransformClasses() 是热补丁的核心操作,但有硬限制
Instrumentation.retransformClasses() 看似万能,实则受 JVM 规范严格约束。以下情况会直接抛 UnsupportedOperationException:
- 修改类的字段数量、类型或访问修饰符(比如加个
private int flag) - 变更方法签名(参数个数、类型、返回值)、增删方法
- 改动
static final常量(JVM 会内联,重定义无效) - 目标类已被
ClassLoader.defineClass()动态生成且未实现getPackage()(某些 OSGi 容器场景)
真正安全的操作只有:仅重写方法体字节码(如修复 if 判空逻辑、绕过某段异常分支)。示例中修复 UserDao.find() 的 NPE,必须保证新字节码和原方法的栈帧结构、局部变量表长度完全一致。
生产环境必须绕开 ClassLoader 隔离和字节码校验
Spring Boot 的 DevTools、某些 APM 工具(如 SkyWalking)也会 hook Instrumentation,可能造成冲突。更关键的是:
- Spring 的 CGLIB 代理类、JDK 动态代理类默认不在
retransformClasses()目标列表里——得先用Instrumentation.getAllLoadedClasses()过滤出原始类(如UserServiceImpl),而非代理名 - 若应用开启了 JVM 字节码验证(
-Xverify:all),部分重写后的字节码会校验失败,需临时关闭(不推荐)或确保 ASM 生成的字节码符合 JSR-202 规范 - 补丁 jar 必须和目标类使用同一 ClassLoader 加载;若类由自定义 ClassLoader(如 Tomcat WebAppClassLoader)加载,agent jar 得设为该 loader 的 parent,否则
ClassNotFoundException
最易被忽略的一点:热补丁生效后,旧方法的 JIT 编译代码不会自动失效。JVM 会等下次 safepoint 清理,期间可能仍执行旧逻辑——观察到“补丁没立刻起效”,不是 bug,是 JVM 的正常行为。











