标记-整理算法专为老年代设计,通过标记存活对象、向低地址端紧凑平移、同步更新所有引用三步闭环消除碎片,确保大对象获得连续空间,避免因碎片导致Full GC或OOM。

标记-整理算法不是“万能清理工具”,而是专为老年代设计的碎片治理机制——它不靠删减对象,而靠重新排布存活对象来换取连续空间,让大对象分配不再卡在“有内存却分不到”的困局里。
为什么老年代特别需要它
新生代对象短命、批量消亡,复制算法高效干净;老年代对象长期驻留,晋升频繁,容易把内存“钉”得七零八落。一个4MB的byte数组、一个含百万元素的HashMap,都需要连续地址。一旦碎片化严重,哪怕老年代还剩30%空闲,也可能因找不到连续块而触发Full GC,甚至直接OOM。
- CMS早期只做标记-清除,运行几小时后常因碎片导致concurrent mode failure,被迫退化为Serial Old全堆整理
- G1虽按Region管理,但混合回收(Mixed GC)本质仍是标记+部分整理,目标就是控制老年代碎片增长速度
- ZGC和Shenandoah用读屏障实现并发整理,核心动机之一就是避免Stop-The-World式的大规模移动,但底层逻辑仍是“标记→重定位→更新引用”
它怎么把内存理顺的
整个过程分三步闭环,缺一不可:
- 标记:从GC Roots出发,遍历所有可达对象,打上“存活”标签。大对象、小对象一视同仁,不会跳过或降级处理
- 整理:不是随机挪动,而是按内存地址顺序,把所有存活对象整体向低地址端紧凑平移。大对象整块迁移,不拆分、不压缩内容,只改起始位置
- 更新引用:栈中变量、对象字段、常量池里所有指向原地址的引用,全部同步刷新为新地址。JVM内部用转发指针或批量修正表完成,确保移动后访问不中断
实际效果与现实约束
它解决的是“能不能分”的问题,而不是“分多快”的问题:
- 成功分配大对象:整理后腾出的大块空闲区,可直接容纳新创建的ArrayList、ByteBuffer等,避免降级到Full GC
- 延缓GC频率:碎片减少→老年代实际可用率提升→晋升对象更易接纳→触发GC的阈值更难达到
- 但整理本身有代价:对象移动+引用更新会延长STW时间。Parallel Old用多线程分摊开销;G1限制每次只整理部分Region;ZGC则把移动和更新拆到并发阶段执行
怎么让它真正起作用
算法再好,不配合参数也白搭:
- 用G1时,-XX:G1HeapRegionSize=1M可减少Region数量,降低碎片粒度;-XX:MaxGCPauseMillis设太低反而抑制整理力度
- CMS用户可加-XX:+UseCMSCompactAtFullCollection,确保每次Full GC后强制整理一次
- 对ZGC,-XX:+ZUncommit能定期把长时间未用的堆页归还OS,缓解虚拟地址空间碎片
- 监控关键指标:jstat -gc输出中O(老年代使用率)持续接近OC(老年代容量),同时GC日志出现to-space exhausted或evacuation failure,就是碎片恶化的明确信号

















