清除阶段并非直接删除未标记对象,而是将其内存空间回收并加入空闲链表供后续分配;为减少外部碎片,需合并物理相邻的空闲块,并可通过延迟清除、位图辅助等策略优化停顿与效率。

“清除”阶段不是简单地把所有未标记对象一删了之,而是回收它们占用的内存空间,供后续分配使用。关键在于如何高效、安全地归还空间,同时避免碎片化或影响并发。
直接将内存块加入空闲链表
这是最基础也最常用的策略。垃圾收集器遍历堆内存,对每个未标记的对象,将其起始地址和大小记录下来,构造一个空闲块描述结构(如 free_chunk),然后插入到全局空闲链表中。后续的内存分配(如 malloc)就从该链表中查找合适大小的块。
- 实现简单,适合单线程、停顿式(Stop-the-world)GC
- 需维护链表头指针和块之间的前后指针,注意指针更新的原子性(尤其在多线程环境下)
- 容易产生外部碎片:小块空闲内存分散,无法满足较大分配请求
合并相邻空闲块(coalescing)
为缓解碎片问题,“清除”阶段常在插入前检查新释放块是否与已有的空闲块物理相邻(即地址连续)。若相邻,就合并成一个更大的空闲块。
- 通常只合并前向(低地址)和后向(高地址)两个方向的空闲块
- 需要快速定位相邻内存块的元信息——常见做法是在每个对象/块头部存储大小及“是否已用”标志,便于向前/向后探测
- 合并能提升大对象分配成功率,但增加清除阶段开销
延迟清除(lazy sweep)
为缩短 GC 停顿时间,有些系统(如 Go 的早期 GC)把“清除”推迟到下一次分配时再执行:未标记对象暂时不处理,分配器在找不到足够空闲空间时,才从前向后扫描堆,跳过已标记对象,将遇到的第一个未标记对象区域当作可用内存返回,并继续推进扫描边界。
- 避免一次性长停顿,“清除”工作被摊平到多次分配中
- 需维护一个“清扫进度指针”(sweep pointer),记录当前扫到的位置
- 首次分配可能稍慢,但整体响应更平滑;需配合写屏障或三色标记防止误回收
位图辅助快速定位
当堆很大时,逐个对象扫描效率低。“清除”阶段可借助预先分配的位图(bitmap),其中每位对应一个内存单元(如一个字或一个对象头)是否存活。清除时只需遍历位图,批量识别连续的未标记区域,再统一加入空闲链表或触发合并。
- 位图本身需额外内存,但访问快、支持并行扫描
- 常与分代 GC 或增量 GC 配合,提高清除局部性
- 位图更新需与标记阶段同步,一般由写屏障或标记线程保证一致性











