Code Cache满导致JIT停止,性能骤降;直接证据是JVM日志中“CodeCache is full. Compiler has been disabled”,可用jstat -compiler查看failed持续增长、compiled停滞,并需启用-XX:+UseCodeCacheFlushing配合扩容。

怎么确认 Code Cache 确实满了
性能下降但 jstack 堆栈分散、jstat -gc 显示 GC 正常、CPU 持续偏高——这些是 Code Cache 耗尽的典型表征。最直接证据在 JVM 启动日志或运行时 stdout/stderr 中:Java HotSpot(TM) 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled. 或更严重的 OutOfMemoryError: requested ... bytes for Chunk::new。
运行中验证可用:jinfo -flag ReservedCodeCacheSize <pid></pid> 查当前配置;再用 jstat -compiler <pid></pid> 看 failed 列是否持续增长(表示 JIT 编译失败)、compiled 是否停滞不前。
为什么增大 -XX:ReservedCodeCacheSize 不一定解决问题
盲目调大参数可能掩盖真实问题,甚至恶化内存压力:
- 设置过大(比如超过堆内存的 20%)会挤占
-Xmx可用空间,导致 GC 更频繁,反而拖慢整体响应 - 如果应用本身生成大量小方法(如 Spring AOP 代理类 + 大量 Lambda + 动态字节码框架),即使缓存翻倍,也可能很快再次填满
- JDK 8 默认分层编译下,
ReservedCodeCacheSize实际默认值可能达 240MB;JDK 11+ 默认更高,但某些容器环境(如 OpenJ9 或精简镜像)可能默认仅 48MB,容易误判为“已调大”
必须配合启用 -XX:+UseCodeCacheFlushing
这个开关才是让 Code Cache “活起来”的关键。它默认关闭,不加就等于只给仓库扩容却不配清仓机制:
- 启用后,当缓存使用率达阈值(约 80%),JIT 会主动驱逐冷代码(非热点、调用频次低的方法),而非等彻底塞满才停编译
- 它不能替代扩容,但能显著推迟甚至避免
Compiler has been disabled的发生 - 注意:该参数在 JDK 8u292+ 和 JDK 11+ 稳定支持;旧版本(如 8u202)可能需额外加
-XX:+UnlockDiagnosticVMOptions
推荐组合启动参数:-XX:ReservedCodeCacheSize=382m -XX:+UseCodeCacheFlushing
哪些代码模式最容易触发 Code Cache 紧张
不是业务逻辑复杂才危险,而是“方法数量爆炸”才致命:
- Spring Boot + CGLIB 代理大量 Controller/Service 接口,尤其配合 @Async/@Transactional 注解时,每个代理类都生成独立方法
- Stream API 中嵌套多层
.map()+.filter()+ Lambda 表达式,每个 Lambda 在字节码层面都是一个独立私有方法 - 热部署工具(如 JRebel、Spring DevTools)或动态类加载框架(如 OSGi、Byte Buddy 运行时织入),持续注入新类,JIT 需反复编译
这类场景下,光调参治标不治本;应结合 jcmd <pid> VM.native_memory summary</pid> 观察 Code 区域增长趋势,并考虑重构减少代理层级或预编译关键路径。
Code Cache 问题难在它不报 OOM,也不进堆 dump,日志里只有一行警告,却能让应用从“飞”变“爬”。真正要盯住的不是数字大小,而是 jstat -compiler 的 failed 值是否归零、以及 UseCodeCacheFlushing 是否真生效——否则扩容只是把崩溃延后几分钟而已。


















