在JVM性能调优过程中,一个常见但令人费解的现象是:老年代明明还有大量空闲空间,Minor GC却仍然抛出Promotion Failed错误。问题根源并非总内存不足,而是内存碎片化所致。

Promotion Failed的直接含义是指,年轻代中的对象在Minor GC完成后无法顺利晋升至老年代。但需要明确的是,这并不代表老年代已经“满了”。真实原因是,老年代中缺乏足够大的连续空闲内存块来容纳这批待晋升的对象。
此问题在采用CMS或Parallel Old等不带压缩功能的回收器时尤为突出。CMS回收器基于“标记-清除”算法,清理后内存天然呈碎片化。假设一个数百KB甚至数MB的大对象——例如大数组、长字符串或缓存块——需要晋升,它必须整块分配,无法拆分。此时,即便老年代剩余总空间高达2GB,只要最大连续空闲块仅有300KB,晋升操作便会失败。
通过日志可以快速识别此类问题,典型信号如下:
[ParNew (promotion failed): 146112K->146112K(153600K), 0.2255230 secs]
年轻代内存使用量几乎不变,说明GC根本没有回收出空间,所有对象都卡在Eden或Survivor区,等待晋升却无法进入。- 紧接着,日志里会出现
[Full GC ...]
JVM启动了兜底机制,退化为Stop-The-World的串行或并行Full GC,停顿时间会显著拉长。 - 如果使用的是CMS,常伴随
CMS-concurrent-mark (concurrent mode failure)
晋升失败导致老年代瞬间失守,CMS还没来得及完成并发标记就被迫中止,转而执行代价更高的Full GC。
因此,Promotion Failed并非简单的“内存不足”,而是“内存充足但无法形成连续大块”。其本质是——老年代碎片化与大对象集中晋升两种因素共同作用的结果。
