老年代未满却触发Full GC常见于晋升失败,本质是空间担保机制响应:老年代剩余连续空间不足(非总容量不足),导致Minor GC后无法容纳晋升对象;典型日志为Promotion Failed,jstat显示OU仅60%~70%;根本原因包括老年代碎片化、Metaspace不足、CMS并发失败或容器内存感知失真。

老年代未满却触发Full GC,常见于晋升失败场景
这不是JVM异常,而是空间担保机制的正常响应。当一次Minor GC后,大量存活对象准备从Eden和From Survivor晋升到老年代,但老年代**剩余连续空间不足以容纳这批对象**(哪怕总空闲容量看起来够),JVM就会提前触发Full GC——它想通过回收老年代碎片或死亡对象,腾出一块足够大的连续空间来完成晋升。
典型现象是GC日志里出现Promotion Failed或Allocation Failure紧随其后就是Full GC;此时用jstat -gc看OU(老年代使用量)可能才60%~70%,远未达100%。
- 根本原因不是“老年代满了”,而是“没有足够连续空间”——这和老年代是否碎片化强相关
-
-XX:+UseCompressedOops开启时,大对象(≥1MB)默认直接进老年代,容易加剧碎片 - 如果Survivor区过小,对象年龄没到阈值就被批量挤向老年代,也会放大晋升压力
Metaspace不足会静默触发Full GC,且不报“老年代满”
OpenJDK 8+已废弃永久代,但Metaspace不足仍会强制触发Full GC,且日志中不会体现老年代使用率飙升。尤其在热部署、动态代理(如Spring AOP)、字节码生成(CGLIB/ASM)频繁的场景下,类元数据持续增长,而MetaspaceSize和MaxMetaspaceSize设得太保守,就会在老年代还很空闲时突然触发Full GC。
验证方式:用jstat -gcmetacapacity 查MU(已用元空间)是否逼近MC(最大元空间);或者加启动参数-XX:+PrintGCDetails,Full GC日志头若带Metadata GC Threshold,基本可锁定。
- 默认
MetaspaceSize仅20.8MB,生产环境几乎必调大(建议至少256MB起) -
MaxMetaspaceSize不设上限时,Metaspace会不断扩容直至耗尽系统内存,反而更容易OOM而非Full GC - 注意容器环境:cgroup memory limit若未被JVM识别(缺少
-XX:+UseContainerSupport),Metaspace可能误判可用内存
CMS收集器的Concurrent Mode Failure本质是“来不及清理”
用CMS的老项目常遇到:老年代使用率才85%就爆Concurrent Mode Failure,紧接着就是Full GC。这不是因为老年代满了,而是CMS并发标记阶段还没跑完,老年代就快被新晋升对象填满了——CMS放弃并发清理,退化为Serial Old单线程Full GC。
关键指标是jstat -gc 里的CCU(concurrent mode failure次数)大于0,同时OU曲线呈现“阶梯式陡升”而非平滑增长。
- CMS已被标记为deprecated(JDK9起),JDK14+彻底移除,升级G1或ZGC是更可持续的解法
- 临时缓解可调
-XX:CMSInitiatingOccupancyFraction=70,让CMS更早启动并发周期 - 但治标不治本:CMS无法处理浮动垃圾,一旦写入突增或晋升加速,Failure极易复现
容器环境下JVM未正确感知cgroup限制,导致GC决策失真
在Docker/K8s中运行OpenJDK,若未启用-XX:+UseContainerSupport(JDK8u191+默认开启,但旧版本或镜像未更新则需显式加),JVM会按宿主机内存大小计算堆上限,比如宿主机64GB,JVM却只分了2GB堆,结果老年代明明还有余量,却因元空间、CodeCache、Direct Memory等其他区域争抢,间接引发Full GC。
典型表现:jstat -gccapacity显示NGCMN/NGCMX(新生代最小/最大容量)异常小,OGCMN/OGCMX也远低于预期;docker stats显示容器内存使用率已超90%,但JVM自身认为“堆还很空”。
- 必须确认JVM启动参数含
-XX:+UseContainerSupport,并配合-XX:MaxRAMPercentage=75.0等合理比例 - OpenSearch/Elasticsearch官方镜像通常已预置,但自定义基础镜像或Java应用打包时容易遗漏
- 用
jinfo -flag +UseContainerSupport可 runtime 验证是否生效
老年代没满就Full GC,真正要盯的不是“用了多少”,而是“能不能放下下一个晋升批次”“元空间还剩几页”“CMS有没有被写入压垮”“容器内存是不是被JVM无视了”。这些点串起来,才是定位真实瓶颈的路径。











