去优化是JIT编译器在运行时主动推翻已编译机器码的安全机制,当乐观假设(如类结构、类型、对象布局)被打破时,立即中止执行、快照状态、重建解释器帧并精准续接字节码。

什么是去优化(Deoptimization)
去优化不是 bug,是 HotSpot JIT 编译器在运行时主动“推翻自己之前编译结果”的行为。它发生在 JIT 已将某段热点代码编译为本地机器码后,发现原先做的假设不再成立——比如某个类被动态加载、某个方法被重定义、某个对象的实际类型超出预测范围。这时 JVM 会强制把正在执行的机器码切回解释执行,并丢弃已编译的版本。
触发去优化的典型场景
以下情况会让 JIT 立刻发起去优化:
-
java.lang.instrument.Instrumentation.redefineClasses()被调用,且修改了已编译方法的字节码 - 使用
Unsafe.defineAnonymousClass()或反射生成新类,导致原先的类继承关系或接口实现发生变化 - 某个
invokespecial或invokevirtual调用点上,JIT 做了单实现(monomorphic)假设,但运行中出现了第二个子类实例 - 逃逸分析后做了标量替换,但后续发生对象逃逸(如被存入静态集合、作为返回值传出),导致栈上分配失效
- 使用
-XX:+UseBiasedLocking(JDK 15+ 已默认关闭)时,偏向锁被撤销且未升级为轻量级锁,触发去优化以清理锁标记
如何确认发生了去优化
必须开启 JVM 日志才能看到真实去优化事件:
- 加参数
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=deopt.log - 在日志里搜索关键词
made not entrant(方法被标记为不可进入)、made zombie(彻底废弃)、deoptimize - 注意:JDK 17+ 默认不打印去优化细节,需额外加
-XX:+TraceDeoptimization
别依赖 jstat -compiler 的 failed 列——它只统计编译失败,不统计成功编译后又被干掉的去优化。
去优化的代价和隐蔽性
一次去优化本身开销不大,但后续影响容易被忽略:
- 被去优化的方法会退回到解释执行,直到再次达到阈值重新编译;这期间性能断层明显,尤其对延迟敏感服务
- 如果频繁触发(比如热更新框架反复 redefineClasses),JIT 编译队列会积压,拖慢整个应用的峰值性能收敛速度
- 去优化不会抛异常、不中断线程,但可能让 JMH 基准测试结果剧烈抖动——因为预热阶段编译的代码中途被废,实际测量跑在解释器上
- 某些场景下(如 GraalVM Native Image 中禁用反射),去优化根本不会发生,反而掩盖了本该暴露的兼容性问题
真正难处理的不是“有没有去优化”,而是“为什么这个看似稳定的代码在线上某台机器上突然开始高频去优化”——这时候往往要查 classloader 行为、JNI 层类型注册、甚至容器环境里的字节码增强 agent。


















