Code Cache耗尽会导致JIT编译器立即停止编译新方法,所有新增热点退化为解释执行,性能下降3–10倍;需用jstat -compiler观察Compiled停滞与Failed增长来确认,调整ReservedCodeCacheSize应预留30%余量并结合jstat -codecache实测峰值。

本地代码缓存(Code Cache)是 JVM 用于存放 JIT 编译后机器码的内存区域。当 -XX:ReservedCodeCacheSize 设置过小,或未显式配置而依赖默认值(JDK8 默认约 240MB,JDK11+ 默认约 256MB),在高负载、多类、高频方法调用场景下极易耗尽。一旦 Code Cache 满,JIT 编译器会停止编译新方法,并将已编译但不再活跃的方法退化(deoptimization)回解释执行——这会导致 CPU 使用率异常升高、吞吐骤降、延迟毛刺增多,且 GC 日志中往往无明显异常,容易误判为业务逻辑问题。
如何确认是 Code Cache 耗尽引发的性能退化
关键线索不是 OOM,而是 JIT 编译行为的静默中断:
- 使用 jstat -compiler <pid> 查看编译统计:若 Compiled 数量长期停滞、Failed 字段持续增长(尤其伴随大量 java.lang.OutOfMemoryError: requested N bytes for ChunkPool::allocate. Out of swap space? 类似日志),基本可锁定
- 启用 JIT 日志:-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation,观察是否出现 made not entrant 或 made zombie 记录,以及编译任务被 disabled 的提示
- 检查 jstat -gccapacity <pid> 和 jstat -gc <pid> 输出是否稳定,排除 GC 频繁干扰;若 GC 正常但响应变慢,需重点怀疑 Code Cache
Code Cache 溢出的典型诱因
并非所有应用都需要大 Code Cache,但以下情况显著提升风险:
- 大量动态生成类(如 Spring AOP 代理、Lombok、MyBatis Mapper 动态代理、Groovy/Scala 混合项目)
- 频繁反射调用 + MethodHandle 或 VarHandle 使用(触发 JIT 针对性编译)
- 开启调试模式(-agentlib:jdwp)或监控代理(如 Prometheus JMX Exporter、SkyWalking Agent),它们自身会注入大量辅助类和适配方法
- 微服务网关类应用(如 Spring Cloud Gateway),路由规则多、Filter 链长、Predicate 编译密集
合理设置与验证方法
不建议盲目堆大数值,应结合实际观测做渐进调整:
- 先用 jstat -compiler <pid> 获取当前峰值 Compiled 方法数,再参考公式粗估:平均每个编译方法占用约 1–3KB 机器码,预留 1.5 倍余量
- 推荐起始值:-XX:ReservedCodeCacheSize=512m -XX:InitialCodeCacheSize=256m(适用于中等复杂度 Spring Boot 服务)
- 上线后持续观察:jstat -compiler <pid> 1000(每秒刷新),确认 Failed 稳定为 0,Compiled 持续缓慢上升而非卡死
- JDK17+ 可配合 -XX:+UseCodeCacheFlushing(默认开启),允许 JVM 在满时主动驱逐冷方法,比硬性扩容更柔性
特别注意兼容性细节
不同 JDK 版本行为差异较大,易踩坑:
- JDK8u292 之前版本存在 Code Cache 泄漏缺陷(JDK-8251039),升级到 u301+ 可规避
- JDK11 开始引入分层 Code Cache(Non-method / Profiled / Non-profiled),-XX:ReservedCodeCacheSize 控制总大小,但各层比例由 JVM 自动分配,不可手动拆分
- AOT 编译(如 .NET 9 或 GraalVM Native Image)完全绕过 Code Cache,但 Java 生态主流仍依赖 JIT,不能作为通用解法

















