Code Cache 占满后 JIT 被硬性禁用而非变慢:Compiled 字段停滞、Failed 持续上升、CCSU ≥ CCMX 且 CCS 不增长,并出现“CodeCache is full. The compiler has been disabled”日志,新热点全部解释执行,性能下降5–10倍。

Code Cache 占满后,JIT 不是变慢或排队,而是直接被禁用——所有新热点方法(包括刚上线的接口、Lambda、Spring 代理、反射调用)全部退回到解释执行,性能通常下降 5–10 倍。这不是 GC 问题,jstat -gc 看不出异常;也不是 CPU 过载,线程堆栈里找不到明显阻塞点。真实信号藏在 JIT 编译器自身的状态里。
怎么确认 JIT 已被硬性关闭?
单看内存使用率容易误判,必须交叉验证三项指标:
-
jstat -compiler
:Compiled 字段长期停滞不涨,Failed 持续上升(每分钟超 100 次即高危),Invalid 数值明显增大 -
jstat -gccapacity
:CCSU(Code Cache used)持续 ≥ CCMX(Code Cache max),且 CCS(current size)不再增长 - 日志关键词:“CodeCache is full. The compiler has been disabled” 或 “hotspotcompilequeue: disabled” —— 这不是警告,是熔断事实
为什么解释执行会导致性能断崖?
解释器逐条解析字节码,无法复用 JIT 提供的关键优化:
- getter/setter 类高频小方法失去内联机会,每次调用都重建栈帧、重复 aload_0/getfield
- for 循环体内的工具方法无法触发 C2 层级优化,循环展开、向量化全部失效
- Spring AOP 代理、MyBatis Mapper 接口等动态生成的方法,首次调用即成热点,却无编译资格,只能走慢路径
- CPU 时间消耗翻倍,请求堆积 → 线程数上涨 → 对象存活时间拉长 → GC 频率被动升高
ReservedCodeCacheSize 调大真能解决问题吗?
盲目加大会掩盖根因,还可能引入新风险:
- Code Cache 是 off-heap 连续内存块,设为 1G+ 容易触发 mmap 分配失败,尤其在容器内存受限时
- 过大的缓存会拖慢 JIT 内部 hash 表查找速度,反而降低编译吞吐
- 可能引发 Compressed Class Space OOM(尤其开启 -XX:+UseCompressedClassPointers 时)
- 对长期运行的微服务,真正瓶颈常是碎片化:还有空闲字节,但最大连续空闲块(largest_free_block)已趋近于 0
更有效的排查与应对思路
优先定位“谁在吃 Code Cache”,再决定是否调参:
- 用 jcmd
VM.code_cache 查各 CodeHeap 段使用率和 largest_free_block,识别碎片化 - 用 jcmd
VM.class_hierarchy | grep -E "(aop|groovy|lombok|cglib)" 统计动态代理类数量 - 启用 -XX:+PrintCodeCache(仅调试),关注 method_count 与 adapter_count 比例:若后者占比 >30%,说明大量胶水代码(Lambda/stub/反射适配器)正在固化占用
- 观察 jstat -codecache
1000 5 的峰值 Used,按 30% 余量设置 ReservedCodeCacheSize(如峰值 180M → 设 256m),单位必须带 m/M










