Code Cache 溢出时JVM立即停止JIT编译,新方法仅解释执行,性能下降3–10倍;已编译代码仍运行,但Lambda、代理等高频路径失去优化;需用jstat -codecache和jstat -compiler确认,调整ReservedCodeCacheSize需预留30%余量并纳入容器内存预算。

Code Cache 满了会直接停 JIT 编译
当 ReservedCodeCacheSize 被耗尽,JVM 会立刻停止编译新方法,所有后续热点代码只能解释执行——性能可能下降 3–10 倍。已编译的代码仍可运行,但新增逻辑、动态代理、Lambda、反射调用等高频路径基本失去优化机会。
这不是 GC 问题,也不是堆内存不足,jstat -gc 和 jstack 都看不出异常,容易误判为“CPU 突然升高但无明显线索”。真实诱因往往藏在 jstat -compiler 的输出里:Compiled 字段停滞增长,Failed 开始上升,Invalid 数值变大。
怎么确认是 Code Cache 溢出导致的?
别猜,用命令直接查:
-
jinfo -flag ReservedCodeCacheSize查当前限制值 -
jstat -compiler看Failed是否持续增加 -
jstat -gccapacity不管用——Code Cache 是独立区域,得用jstat -codecache - 加启动参数
-XX:+PrintCodeCache,JVM 启动和每次 GC 后都会打印当前使用率(注意:仅限调试,线上慎开)
如果 Used 接近 Max,且 Unallocated Capacity 长期为 0,基本就是它了。
调整 ReservedCodeCacheSize 的实操要点
盲目加大会浪费内存,设太小又反复触发降级。OpenJDK 11+ 默认是 240M,但很多老服务还在用 JDK 8u202 之前的版本,默认只有 48M,风险极高。
- 先观察峰值:用
jstat -codecache每秒采样 5 次,看1000 5 Used最高冲到多少 - 预留 30% 余量:比如峰值 180M,就设
-XX:ReservedCodeCacheSize=256m(必须带单位,m或M,不支持g) - 不要设成 1g 或 2g:Code Cache 是连续内存块,过大易导致 mmap 分配失败,尤其在容器中内存受限时
- JDK 17+ 支持
-XX:+UseG1GC与 Code Cache 更好协同,但ReservedCodeCacheSize仍需手动调优
UseCodeCacheFlushing 不是万能解药
-XX:+UseCodeCacheFlushing 在 JDK 7u4+ 默认开启(JDK 8u20 及之后强制启用),但它只在 Code Cache 将满时尝试清理“冷代码”,比如长时间未被执行的 profiled 方法体。清理失败或清理后仍不足,JIT 依然停摆。
- 它不能解决长期累积型膨胀:比如 Spring AOP 生成大量代理类、频繁 redefine class(热部署)、大量 Groovy/JSR-223 脚本编译
- 开启后可能引发微小 STW(Stop-The-World),虽短但对延迟敏感服务需实测
- 真正治本要结合代码层:减少运行时字节码生成、避免无节制的 Lambda 嵌套、控制 CGLIB/AOP 代理粒度
最常被忽略的一点:容器环境里,ReservedCodeCacheSize 是 JVM 进程独占内存,不计入 cgroup memory limit,超限会导致 OOMKilled 却查不到堆外内存泄漏——务必把 Code Cache 预估容量纳入容器内存预算。










