Code Cache满载后JIT立即永久停止编译,直至JVM重启或触发刷新;最直接证据是日志出现“CodeCache is full. The compiler has been disabled.”。

Code Cache 满载后 JIT 立即停止编译,且不会自动恢复 —— 这不是临时降级,而是永久性功能禁用,直到 JVM 重启或主动触发刷新(前提是未关闭 UseCodeCacheFlushing)。
如何确认 Code Cache 已满并导致 JIT 停摆
最直接的证据是日志中出现明确提示,而不是靠猜测或性能下降倒推:
-
CodeCache is full. The compiler has been disabled.—— OpenJDK 8/11/17+ 通用告警,一旦出现即代表 JIT 编译器已全局停用 - JVM 启动后持续输出
CompilerOracle: exclude ...类日志,但实际无新方法被编译(可通过jstat -compiler <pid></pid>验证:failed列持续增长,compiled列长时间停滞) -
jstat -gccapacity <pid></pid>不反映问题,必须用jstat -compiler <pid></pid>或jstat -printcompilation <pid></pid>查看实时编译状态 - NMT(Native Memory Tracking)中
Code分类的 usage 达到Reserved上限,且committed≈reserved,说明无冗余空间
为什么 -XX:+UseCodeCacheFlushing 不一定管用
该参数控制 JVM 是否尝试从 Code Cache 中驱逐冷代码来腾出空间。但它有硬性前提和限制:
- 默认开启(JDK8u262+、JDK11+),但仅在
SegmentedCodeCache启用时才生效;若使用-XX:-SegmentedCodeCache(如某些旧调优场景),则刷新逻辑完全不工作 - 驱逐只针对
NonProfiledCode段,而ProfiledCode和NonNMethodCode(如 stubs、adapters)无法被清理,容易卡死在临界点 - 当 Code Cache 使用率 >95% 且连续多次驱逐失败(例如因所有方法都被标记为“hot”),JVM 会放弃刷新并彻底禁用编译器
- 某些 JDK 版本(如 OpenJDK 11.0.14)存在
UseCodeCacheFlushing在高压力下失效的 bug,需打补丁或升级
哪些场景最容易触达 Code Cache 瓶颈
不是代码量大就一定爆,而是特定模式会加速耗尽:
- 大量动态生成类(Lombok @Builder、MapStruct、Spring AOP CGLIB 代理、Groovy 脚本)—— 每个类的桥接方法、accessor、lambda metafactory 都产生独立编译单元
- 频繁 retransform/redefine(Arthas、JRebel、某些监控 agent)—— 新版本字节码触发重复编译,旧版本未必及时淘汰
- 启用了
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly且未限制范围 —— JIT 输出汇编会额外占用 Code Cache 空间(尤其在调试阶段) - 微服务高频启停(K8s Pod 快速重建)—— 每次启动都重新 JIT,但 Code Cache 不跨进程复用,短生命周期应用更容易在单次运行中堆满
- AArch64 平台比 x86_64 多消耗约 20–30% Code Cache(指令编码更长),相同配置下更容易溢出
线上应急与长期规避的关键操作
不能只靠加大 -XX:ReservedCodeCacheSize,要分层处理:
- 紧急止损:
jcmd <pid> VM.native_memory summary.scale=MB</pid>确认当前 Code Cache usage;若已满,唯一可靠手段是滚动重启(避免 Full GC 误伤) - 观察基线:
jstat -compiler <pid> 2000</pid>每 2 秒打印一次,关注total和failed差值是否收敛 —— 若 5 分钟内无新增 compiled,基本可判定 JIT 已停 - 合理设限:
-XX:ReservedCodeCacheSize=512m是较安全起点(OpenJDK 11+),但需配合-XX:InitialCodeCacheSize=128m避免启动期反复扩容锁竞争 - 精准排除:
-XX:CompileCommand=exclude,com.example.*.*对已知非热点包(如 DTO、config 类)跳过编译,比全局关 JIT 更可控 - 警惕“伪解法”:
-XX:-TieredStopAtLevel=1强制只用 C1 编译器,虽减少 Code Cache 单方法体积,但牺牲了 C2 的深度优化,且仍可能填满
真正难处理的是那些既无法剔除、又高频生成的代码路径 —— 比如框架自动生成的泛型桥接方法或响应式链中的匿名 lambda。这类问题不会在日志里报错,只会让 jstat -printcompilation 的输出越来越稀疏,最终安静地拖垮吞吐量。










