老年代需要压缩是因为其对象存活率高、生命周期长,易产生外部碎片,而年轻代通过复制算法天然紧凑无需压缩;压缩通过整理存活对象腾出大块连续空间,消除碎片并提升缓存局部性,但需STW移动对象和更新引用。

老年代的物理优化逻辑,核心不在“清理”,而在“整理”——即通过压缩(Compaction)把零散存活对象聚拢到连续内存段,腾出大块空闲空间。这和年轻代靠复制算法自然实现紧凑不同,老年代必须显式执行移动与重排,才能应对长期运行后产生的严重外部碎片。
为什么老年代需要压缩,而年轻代不需要?
年轻代采用复制算法:每次 Minor GC 都只把存活对象复制到另一块 Survivor 区或老年代,原区域直接清空,天然无碎片。老年代则不然——它用标记-清除或标记-整理算法,对象存活率高、生命周期长,频繁移动成本太高,所以默认不自动压缩;但一旦碎片化严重(比如大量中等大小对象被逐个释放),就会出现“空闲总和足够,却无法分配一个 2MB 对象”的情况。此时,只有压缩能打破这种僵局。
老年代压缩发生的典型场景
- Full GC 触发时:当老年代空间不足,且无法通过 Minor GC 缓解,JVM 会启动 Full GC;若选用 G1、ZGC 或 Serial Old 等支持压缩的收集器,就会在标记阶段后执行压缩,把存活对象向一端滑动。
- 分配担保失败后:Minor GC 时若 Survivor 区放不下存活对象,需晋升至老年代;但老年代剩余连续空间小于待晋升对象大小,就会触发担保失败(Promotion Failure),进而强制进入带压缩的 Full GC。
- G1 的 Mixed GC 阶段:G1 不是全堆压缩,而是选择若干已回收价值高的老年代 Region,连同 Eden 和 Survivor 一起回收,并对这些 Region 内部做局部压缩,实现增量式整理。
压缩如何影响老年代的物理布局与性能?
压缩前,老年代内存呈“马赛克状”:存活对象分散,中间夹杂大量小空洞;压缩后,所有存活对象被搬移到低地址连续区域,高地址形成一大片空白。这种布局带来两个关键优化:
- 消除外部碎片:大对象可直接分配,不再因找不到连续空间而提前触发 GC 或 OOM;
- 提升缓存局部性:对象在内存中相邻存放,CPU 访问时更容易命中 L1/L2 缓存,减少内存延迟。
但代价也很明确:压缩过程需暂停应用线程(Stop-The-World),移动对象意味着更新所有指向它的引用,耗时随老年代存活对象数量线性增长。因此,现代 GC 器(如 ZGC、Shenandoah)用读屏障+并发标记+染色指针等技术,把压缩操作拆成多个小步,在应用运行时渐进完成,大幅缩短停顿时间。
调优中如何间接控制老年代压缩行为?
你不能直接“开启/关闭压缩”,但可通过参数引导 JVM 更早、更少或更高效地执行它:
- 增大老年代预留空间:用 -XX:CMSInitiatingOccupancyFraction(CMS)或 -XX:G1HeapWastePercent(G1)降低触发阈值,避免等到极度碎片化才行动;
- 限制对象过早晋升:调小 -XX:MaxTenuringThreshold 或增大 Survivor 区(-XX:SurvivorRatio),让短命对象在年轻代就消亡,减少老年代无效驻留;
- 避免大对象直入老年代:合理设置 -XX:PretenureSizeThreshold,防止突发的大数组反复撕裂老年代连续空间。










