标记整理算法

标记清除算法分为两个阶段:

  1. 标记阶段:遍历所有可达对象(从GC Root开始),标记它们为“存活”。
  2. 清除阶段:遍历整个堆内存,回收所有未被标记为“存活”的对象占用的空间。

标记整理


算法步骤详解

  1. 暂停应用程序线程

    • 垃圾回收过程通常需要暂停所有应用程序线程,这被称为 “Stop-The-World” 停顿。这是为了确保在标记过程中对象引用关系不会发生变化,保证垃圾回收的正确性。标记-清除算法的 STW 时间主要发生在标记阶段(也可能在清除阶段)。
  2. 标记阶段
    起点:

    • 虚拟机栈(栈帧中的局部变量表)中引用的对象。

    • 方法区中类静态属性引用的对象。

    • 方法区中常量引用的对象(如字符串常量池里的引用)。

    • 本地方法栈中 JNI(即 Native 方法)引用的对象。

    • 所有被同步锁持有的对象。

    • JVM 内部引用(如系统类加载器、基本类型对应的 Class 对象)。

    遍历:

    • 访问一个对象。

    • 将其标记为“存活”(通常是在对象头中设置一个标记位)。

    • 递归地访问并标记这个对象引用的所有其他对象。

    结果:

    • 所有从 GC Roots 可达的对象都会被标记为“存活”。而无法从任何 GC Roots 访问到的对象,则被认为是“垃圾”。
  3. 清除阶段:
    遍历堆:线性(或按某种顺序)遍历整个堆内存的地址空间。
    回收垃圾:对于堆中的每一个位置(或对象):

    • 如果该位置的对象未被标记,则认为他是垃圾。
    • 回收该对象占用的内存空间,回收通常不是立即擦除数据,而是将这个空间记录到一个空闲列表中,供后续新对象分配使用。

优点:

  • 概念简单,易于理解: 算法流程非常直观。

  • 实现相对容易: 早期的垃圾收集器常采用此算法。

  • 无对象移动开销: 在清除阶段,存活对象的位置没有发生移动。这对于大对象或存活时间长的对象(如老年代对象)来说是个优势,避免了复制带来的开销。

  • 空间利用率(理论上): 回收的内存可以立即放入空闲列表供分配。

缺点:

  • 内存碎片:这是标记-清除算法最严重的问题。
  • 效率问题:标记阶段需要遍历所有存活对象,清除阶段需要遍历整个堆(包括存活对象和垃圾对象)。对于大堆,这两个遍历过程都可能比较耗时,导致较长的 STW 停顿时间。
  • 分配效率:由于存在内存碎片,分配新对象时需要遍历空闲列表(Free List)来寻找合适大小的空闲块。这比从连续空间(如复制算法中的 To 区)分配要慢。
  • 空间浪费:碎片本身导致一部分空间无法被有效利用。
posted @ 2026-01-15 10:12  panda_an  阅读(2)  评论(0)    收藏  举报