最直接、最可靠的信号是jstat -compiler的failed列持续增长且compiled停滞,表明jit已被禁用;需用jstat -codecache查各段used与maxused是否打满或接近95%,并用jcmd vm.code_cache确认largest free block是否远小于空闲容量以识别碎片化。

看 jstat -compiler 的 failed 是否持续增长
这是最直接、最可靠的信号。JIT 编译失败不是偶发抖动,而是系统性停摆的前兆:failed 列每秒都在涨,compiled 却停滞不动,基本可断定 JIT 已被禁用。
常见错误现象包括:
- JVM stdout/stderr 中出现
CodeCache is full. Compiler has been disabled. - 应用运行后期 CPU 持续偏高,但
jstat -gc显示 GC 正常、堆内存稳定 - 新增方法(如 Spring AOP 代理、Lambda 表达式、动态脚本)执行明显变慢,而老代码无变化
用 jstat -codecache 查各段真实使用率
jstat -gccapacity 不管用——Code Cache 是独立内存区,必须用专用命令。JDK 8u202+ 和所有 JDK 11+ 都支持 jstat -codecache <pid></pid>,它会显示三个关键段:
-
NonProfiledCode:未启用 profiling 的编译代码(如 C1 编译的简单方法) -
ProfiledCode:带调用计数信息的编译代码(C2 主要产出) -
NonNMethodCode:JNI stub、适配器等非方法体代码
重点不是看 Used 绝对值,而是比对 Used 和 MaxUsed:如果某段 Used == MaxUsed,说明该段已彻底打满;若三段都接近上限(比如均 ≥95%),但 Unallocated Capacity 为 0,就是典型的容量瓶颈。
查 jcmd <pid> VM.code_cache</pid> 看碎片化是否严重
即使 Used 只有 70%,也可能因内存不连续而触发 not enough contiguous free space left 报警。这时 jstat -codecache 会显示大量空闲但无法分配。
运行 jcmd <pid> VM.code_cache</pid>,关注输出中的 largest free block:
- 如果它远小于
CodeCache Capacity - CodeCache Used,就是碎片化证据 - OpenJDK 11+ 还可加
-XX:+PrintCodeCacheStatisticsOnExit,压测后看退出日志里的空洞分布 - 碎片问题在长期运行、频繁 redefine class(热部署)、大量 Groovy/JSR-223 脚本的场景中尤为突出
别只信 jinfo -flag ReservedCodeCacheSize 的默认值
这个命令只能告诉你当前设了多少,但不能说明够不够。JDK 版本和运行环境差异极大:
- JDK 8u202 之前默认仅
48m,极易溢出;JDK 11+ 默认240m或更高 - 容器环境(尤其 OpenJ9 或精简镜像)可能继承极小默认值,你以为“已调大”,其实还是 48m
- Spring Boot + AOP + WebFlux + 大量 Lambda 的服务,实测峰值常超 200m,预留 30% 余量才稳妥
真正要做的,是用 jstat -codecache <pid> 1000 5</pid> 采样 5 秒,记下 Used 最高值,再决定是否扩容——而不是靠版本文档猜。











