标记整理

标记整理分为两个步骤

1.标记阶段与标记-清除算法完全一致。遍历所有可达对象(从 GC Roots 开始),标记它们为“存活”。

2.整理阶段:不再是简单清除垃圾对象,而是将所有存活的对象向内存空间的一端(通常是起始地址或结束地址)移动,紧凑排列。移动完后,边界之外的内存空间全部视为空闲空间,可以一次性分配。


标记

算法步骤详解

  1. 暂停应用程序线程:
  • 同样需要 “Stop-The-World” 停顿,以确保对象引用关系在标记和移动过程中保持稳定。标记-整理算法的 STW 时间通常比标记-清除更长,因为它包含了对象的移动开销。
  1. 标记阶段****:
  • 起点:从GC Root开始。
  • 遍历:采用深度优先搜索或广度优先搜索遍历对象图。
  • 标记:将所有从GC Roots可达的对象标记为“存活”。结果与标记清除算法相同。
  1. 整理阶段****:
  • 计算新地址:
    • 遍历堆内存(通常是线性扫描)。
    • 计算每个存活对象在整理后应该被移动到的新地址。新地址通常是连续的,从堆空间的某一端(如低地址端)开始排列。
    • 一种常见的策略是:维护一个“指针”(compaction pointer),初始指向目标区域(如堆起始地址)。每遇到一个存活对象,就计算其大小,将该对象的新地址设置为当前指针位置,然后将指针向后移动该对象大小的距离。
  • 更新引用:
    • 由于对象被移动了位置,所有指向这些移动对象的引用(包括GC Root中的引用和存活对象内部字段的引用)都需要被更新为对象的新地址。
    • 再次遍历对象图(从GC Roots开始),访问所有存活对象及器引用,将指向被移动对象的引用值修改为计算好的新地址。
    • 关键点:更新引用必须在对象实际移动之前完成,或者在移动过程中使用特殊的技巧(如 Brooks 指针)来保证引用的正确性。否则,移动对象后,旧的引用就会指向无效地址。
  • 移动对象:
    • 遍历堆内存
    • 将每个存活的对象复制(移动)到之前计算好的新地址
    • 移动完成后,所有存活对象都被紧密地排列在堆空间的一端(如低地址端)。
  • 回收空间:
    • 移动完成后,从最后一个存活对象之后的位置开始,直到堆空间的另一端(如高地址端),所有内存空间都是连续的空闲空间。
    • 这个连续的大块空闲内存可以被一个简单的指针(如 bump pointer)管理,新对象的分配变得极其高效(只需移动指针并清零内存)。

优缺点

  1. 优点:
  • 解决内存碎片: 这是最核心的优势!通过将存活对象紧凑排列,消除了标记-清除算法产生的内存碎片问题。分配新对象时,只需要在连续空闲空间的末尾进行指针碰撞(bump-the-pointer),分配速度非常快且简单。
  • 空间利用率高: 消除了碎片浪费,堆空间得到更有效的利用。特别是对于需要分配大对象的场景,成功率更高。
  • 局部性原理提升: 紧凑排列的对象在内存中位置相邻,更有可能被加载到 CPU 缓存同一缓存行(Cache Line)中。这可以提升程序访问对象的效率(缓存命中率提高)。
  1. 缺点:
  • STW 时间更长: 移动对象和更新引用的开销通常比简单的清除操作要大得多。这导致 Stop-The-World 停顿时间通常比标记-清除算法更长,尤其是在堆很大、存活对象很多的情况下。这是标记-整理算法最主要的缺点。
  • 移动开销: 复制对象本身需要时间,尤其是对于大对象。
  • 更新引用开销: 需要遍历整个对象图来更新所有指向被移动对象的引用,这也是一个耗时的操作。
  • 实现更复杂: 需要精确地处理对象移动和引用更新,实现难度高于标记-清除算法。
posted @ 2026-01-16 15:49  panda_an  阅读(1)  评论(0)    收藏  举报