标记-整理算法(mark-compact)先标记所有可达对象,再将存活对象顺序搬移到堆的一端以消除碎片,最后统一更新所有指向旧地址的引用。

标记-整理算法(Mark-Compact)在垃圾回收中通过“标记”存活对象、“整理”内存空间来消除碎片。其核心在于:先遍历并标记所有可达对象,再将它们顺序搬移到内存一端,最后批量更新所有指向这些对象的指针。
标记阶段:识别存活对象
从根集合(如栈帧、寄存器、静态变量)出发,用深度优先或广度优先遍历对象图,为每个可达对象打上标记位(如对象头中一个 bit)。未被标记的对象即判定为垃圾,后续不参与移动。
- 标记过程不修改对象内容,仅设置标识,开销相对可控
- 需保证并发场景下标记一致性(如使用三色标记或写屏障)
- 标记完成后,可快速统计存活对象总数和总大小,用于规划整理区域
整理阶段:按序搬移存活对象
从堆起始地址开始扫描,维护一个“目标位置”指针(如 freePtr),每遇到一个已标记对象,就将其完整复制到 freePtr 所指位置,然后将 freePtr 向后推进该对象大小。
- 搬移顺序通常与原始内存布局一致(保持局部性),也可按对象大小分组以优化缓存友好性
- 对象复制时需保持字段对齐、保留元数据(如类指针、哈希码等)
- 搬移后原位置可直接清零或留作下次分配起点,无需单独清理
指针更新阶段:修正所有引用
整理完成后,所有存活对象已紧凑排列,但程序中仍存在大量指向旧地址的引用(如栈、寄存器、其他对象字段)。必须遍历所有可能含指针的位置,将旧地址替换为新地址。
- 常用方法是预先记录每个对象的“移动映射”:旧地址 → 新地址(可用哈希表或位图压缩存储)
- 栈和寄存器中的指针由 GC 线程直接扫描并更新;堆内引用则遍历所有存活对象的字段逐个修正
- 现代实现常结合“卡片标记”或“写屏障”减少需扫描范围,避免全堆遍历
关键细节:移动与更新的协同保障
移动和更新必须严格有序:不能在对象尚未搬完时就开始更新指针,否则会引用到中间状态;也不能延迟更新导致程序读到失效地址。
- 典型做法是两阶段执行:第一阶段只搬移,第二阶段统一更新——确保原子性
- 部分 JVM 实现(如 Serial GC)采用“滑动压缩”,边搬移边计算新地址,但更新仍集中进行
- 若支持并发整理,需额外机制(如读屏障)拦截访问,防止程序看到半移动对象











