
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> 验证:failed 列持续增长,compiled 列长时间停滞)jstat -gccapacity <pid> 不反映问题,必须用 jstat -compiler <pid> 或 jstat -printcompilation <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 确认当前 Code Cache usage;若已满,唯一可靠手段是滚动重启(避免 Full GC 误伤)观察基线:jstat -compiler <pid> 2000 每 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 的输出越来越稀疏,最终安静地拖垮吞吐量。

评论(0)