标记-整理算法核心目标是消除内存碎片,通过将存活对象向堆一端移动形成连续已用区和空闲区,提升大对象分配成功率与内存利用率;其压缩过程伴随对象重定位、引用更新和stw停顿,开销较大,故适用于老年代等低频高存活场景。

标记-整理算法本身并不追求“高效压缩”,它的核心目标是消除碎片,而非压缩速度。所谓“高效”在该算法中是相对的——它比复制算法在老年代更省空间,比标记-清除更能保障后续大对象分配成功率,但其压缩过程天然伴随开销。
压缩不是“快”,而是“紧凑且可控”
整理阶段将所有存活对象向堆内存一端(通常是低地址端)移动,形成连续的已用区和连续的空闲区。这种布局让后续对象分配只需维护一个“空闲指针”(如 bump-the-pointer),避免了空闲链表查找或最佳适配等复杂策略。
关键在于:压缩后内存呈线性结构,分配逻辑极简,长期看提升了吞吐稳定性,而不是单次回收耗时更短。
压缩过程的关键操作
- 存活对象重定位:按遍历顺序(如深度优先或按地址升序)将对象逐个复制到目标区域,保持相对顺序以减少缓存抖动。
- 引用地址批量更新:栈帧、寄存器、其他对象字段中所有指向原地址的引用,必须同步修正为新地址。现代JVM通常借助OopMap辅助快速定位引用位置,避免全堆扫描。
- 边界管理:使用“移动指针”(move pointer)和“扫描指针”(scan pointer)双指针协作完成整理,无需额外空间记录映射关系。
为什么它不适合频繁执行
每次整理都需:
- 遍历全部存活对象并复制数据(哪怕只改地址也要触达)
- 遍历所有GC Roots及堆内引用字段,更新指针
- 暂停所有Java线程(STW),停顿时间与存活对象数量正相关
因此,Serial Old、Parallel Old 等基于该算法的收集器,主要用在老年代——那里对象存活率高、回收频率低,压缩收益远大于移动成本。
现代JVM如何缓解压缩开销
真正“高效”的实践,是规避全堆整理:
- G1 的混合回收:只对部分老年代Region进行标记+局部整理(evacuation),不移动整个老年代
- ZGC / Shenandoah:通过读屏障+并发转移,在应用线程运行中完成对象重定位,大幅缩短STW
- 分代假设利用:新生代用复制算法快速清理,仅老年代才启用标记-整理,降低整体压缩频次











