Java 21代码缓存已满却未触发Full GC的原因及参数咨询
你指出的JDK-8290025正是导致该行为变化的核心原因,这个JDK变更彻底调整了Code Cache垃圾回收的触发逻辑,尤其针对Serial GC场景:
- Java 17及更早版本中,Code Cache达到阈值时会主动触发Full GC来清理失效的编译代码(nmethod);
- 升级到Java 21后,Code Cache的清理被绑定到堆的Full GC周期——只有当堆内存(尤其是老年代)触发Full GC时,才会顺带执行Code Cache清理。你的服务老年代占用率极低,没有堆层面的Full GC触发条件,因此Code Cache即便填满也不会主动触发Full GC。
你尝试的-XX:SweeperThresholdStartAggressiveSweepingAt等参数未生效,是因为这些参数仅控制Code Cache的激进清理策略,无法绕过21版本新增的“依赖堆Full GC触发”的逻辑——没有堆Full GC的信号,这些参数无法单独驱动Code Cache清理。
针对Serial GC下的Code Cache清理问题,推荐尝试以下参数:
让Code Cache清理与Young GC绑定
添加参数:-XX:+CodeCacheFlushingOnYoungGC
该参数会让JVM在每次Young GC时尝试清理Code Cache,无需等待Full GC,适配你服务老年代无压力的场景,能定期清理失效编译代码。调整Code Cache阈值触发逻辑
XX:CodeCacheMinFreeSpace=2m:设置Code Cache的最小空闲空间阈值,当空闲空间低于该值时触发清理尝试;-XX:CodeCacheExpansionSize=1m:减小Code Cache的扩容步长,避免一次性占用过多空间,让阈值触发更敏感。
启用分段Code Cache(长期推荐)
Java 21默认支持分段Code Cache(分为NonNMethod、Profiled、NonProfiled三个独立区域),不同区域可独立清理,降低整体填满概率。如果你的服务当前使用非分段模式,可添加参数开启:-XX:+SegmentedCodeCache -XX:ReservedCodeCacheSize=40m -XX:NonProfiledCodeCacheSize=20m -XX:ProfiledCodeCacheSize=15m临时验证用:强制定期Full GC(不推荐生产)
若需快速验证Code Cache是否能在Full GC时被清理,可添加-XX:+ExplicitGCInvokesConcurrent并在代码中定时调用System.gc(),但这会引入不必要的Full GC开销,仅用于排查问题。
- 用
jcmd <pid> Compiler.codecache命令查看Code Cache各区域的占用明细,确认是哪类代码(如Profiled方法)占满缓存; - 调整
-XX:CompileThreshold降低JIT编译触发阈值,减少临时代码对Code Cache的占用。
内容的提问来源于stack exchange,提问作者Jesper

