垃圾回收算法

自动内存管理的核心算法——从引用计数到并发增量 GC。

概念

垃圾回收 (Garbage Collection) 是托管运行时的基础设施。与 C 的 malloc/free 不同,GC 自动判定对象何时不再可达并回收其内存。在游戏引擎、数据库和操作系统中,极致的 GC 暂停时间是关键设计目标。本文件覆盖七种核心 GC 策略。

GC 算法矩阵

算法回收精度内存碎片Stop-The-World典型应用
引用计数 (Ref Counting)精确低 (可配合 compact)无 (分散到每次操作)CPython, Objective-C, Swift (ARC)
标记-清除 (Mark-Sweep)精确全程 STWRuby MRI, 早期 Lisp
标记-压缩 (Mark-Compact)精确全程 STW.NET 大对象堆
复制 (Copying / Scavenge)精确全程 STW (但短)V8 新生代, JVM Survivor
分代 (Generational)精确仅新生代 STWV8, JVM, .NET
并发标记 (Concurrent Mark)精确仅快照/remarkG1GC, ZGC, Go GC
引用计数 + 循环检测精确循环检测时 STWCPython 循环收集

标记-清除

算法 Mark-Sweep:
    标记阶段:
        从 GC Roots (栈变量, 全局变量, 寄存器) 出发
        深度优先或广度优先遍历对象图
        每个可达对象打上标记 (mark bit = 1)

    清除阶段:
        线性遍历整个堆:
            IF 对象未标记:
                free(object)  # 加入空闲链表
            ELSE:
                清除标记 (为下次 GC 做准备)

标记-压缩

算法 Mark-Compact (二指法):
    # 第一阶段: 标记 (同 Mark-Sweep)
    # 第二阶段: 计算新地址
    free = heap_start
    FOR 每个存活对象 obj (按地址顺序):
        obj.forwarding_address = free
        free += obj.size

    # 第三阶段: 修正指针
    FOR 每个存活对象 obj:
        FOR 每个引用字段 ref IN obj:
            ref = ref.forwarding_address

    # 第四阶段: 移动对象
    FOR 每个存活对象 obj:
        memmove(obj.forwarding_address, obj, obj.size)

复制 GC (Scavenge)

算法 Cheney 复制:
    # 堆分为 From 空间和 To 空间
    scan = To_start
    free = To_start

    # 复制所有 GC Roots 指向的对象到 To 空间
    FOR 每个 root:
        obj = *root
        *root = copy_to_tospace(obj)   # 复制 + 设置 forwarding

    # 广度优先复制
    WHILE scan < free:
        遍历 scan 指向对象的每个引用字段 ref:
            IF ref 在 From 空间:
                *ref = copy_to_tospace(*ref)
        scan = scan + scan->size

    # 完成后, From 空间整块释放
    swap(From, To)

分代 GC

算法 分代假说:
    "大多数对象朝生夕死" (Weak Generational Hypothesis)
    "老对象很少引用新对象"

    堆分为:
        Nursery (新生代 / Young Generation):
            新分配的对象
            频繁 GC (Minor GC)
            使用复制算法 (Scavenge)

        Tenured (老生代 / Old Generation):
            经历了若干次新生代 GC 后存活的对象
            低频 GC (Major GC / Full GC)
            使用标记-压缩或并发标记

    写屏障 (Write Barrier):
        当老对象引用新对象时触发记录
        避免 Minor GC 扫描整个老生代

引用计数

算法 朴素引用计数:
    每个对象有一个 ob_refcnt 字段

    INCREF(obj):
        obj.ob_refcnt += 1

    DECREF(obj):
        obj.ob_refcnt -= 1
        IF obj.ob_refcnt == 0:
            # 级联释放
            FOR 每个 obj 引用的 child:
                DECREF(child)
            free(obj)

    问题: 循环引用
        a.next = b; b.prev = a
        a.ob_refcnt = 2  (来自: 栈变量, b.prev)
        b.ob_refcnt = 2  (来自: a.next, 栈变量)
        栈变量离开作用域:
            DECREF(a) → a.ob_refcnt = 1 (b.prev 仍引用)
            DECREF(b) → b.ob_refcnt = 1 (a.next 仍引用)
        两者永不回收 → 内存泄漏!

    解决: 循环检测器 (CPython 的 gc 模块)
        只检测容器类型 (list, dict, set, 用户类)
        标记可达性分析 + 清理不可达循环的引用计数