防止jit编译器对核心代码“擦除”的关键是jvm启动参数+运行时工具+jstat监控+代码设计协同:一、用jstat -codecache和jstat -compiler实时监控used/max比值与failed计数;二、启动时设-xx:reservedcodecachesize(如256m),禁用system.setproperty伪配置;三、用-xx:compilecommand锁定核心方法,避免动态代理或redefine触发去优化;四、启用-xx:+useg1gc并纳入容器内存预算,抑制碎片与元数据失效。

一、用 jstat 实时盯住 Code Cache 状态
Code Cache 被填满是 JIT 代码“消失”的最常见原因,而 System 类完全无法感知它。必须用 JVM 自带工具主动查:
-
jstat -codecache <pid></pid>:看 Used / Max 比值,持续 ≥90% 就危险,说明缓存快撑爆,新编译会失败、旧代码可能被强制清理 -
jstat -compiler <pid></pid>:关注 Failed 列是否增长,一旦非零且上升,代表 JIT 已开始放弃编译,核心方法可能长期停留在 C1 或解释态 - 别等报警——把这两个命令写进巡检脚本,每30秒采一次,比值超85%就发告警
二、设对 ReservedCodeCacheSize,不靠 System.setProperty
有人误以为调用 System.setProperty("XX:ReservedCodeCacheSize", "256m") 能生效,其实完全无效:该参数必须在 JVM 启动时由 VM 解析,运行时 setProperty 不影响底层 mmap 分配。
- 先用
jstat -codecache <pid> 1000 10</pid>观察10秒内峰值用量 - 按峰值 × 1.3 设置,例如实测峰值 192MB → 启动加
-XX:ReservedCodeCacheSize=256m(注意单位小写 m) - JDK 8 建议至少设为 256m(默认仅 48m/96m),OpenJDK 11+ 默认 240m 可作起点
三、锁定核心方法不被去优化
“擦除”常源于去优化(deoptimization):比如新类加载打破单实现假设,导致 JIT 编译好的热点方法被炸开回解释执行。这不是缓存满了,而是运行时契约被破坏。
- 用
-XX:+PrintMethodData -XX:CompileCommand=print,*YourCoreClass.yourHotMethod查 profile 数据,确认 receiver types 是否稳定(只1–2种类型占绝对主导) - 若实际是伪多态,就重构:用策略枚举 + switch 替代泛型接口调用,让 JIT 能做可靠去虚拟化
- 避免在核心路径上动态生成类(如 Spring AOP 代理、Groovy eval),否则每次 redefine 都可能触发整块代码去优化
四、配合 GC 策略稳住编译生命周期
某些 GC 行为会间接清空 JIT 元数据或强制刷新 Code Cache。例如 CMS 并发模式或频繁 Full GC,会让 C2 编译的 profiled 方法失效。
- 生产环境优先启用
-XX:+UseG1GC:G1 对 Code Cache 的元数据管理更友好,显著降低碎片概率 - 禁用
-XX:+UseConcMarkSweepGC或-XX:+UseParallelGC(尤其在高 JIT 密度场景) - 容器中部署时,把
ReservedCodeCacheSize纳入 cgroup 内存预算,防止系统因 RSS 超限回收掉 JIT 区域











