Golang 垃圾回收(GC)原理

1,184 阅读3分钟

这是我参与更文挑战的第4天,活动详情查看: 更文挑战
如果❤️我的文章有帮助,欢迎点赞、关注。这是对我继续技术创作最大的鼓励。更多往期文章在我的个人博客

Golang 垃圾回收(GC)原理

什么是垃圾回收(GC)

垃圾回收算法 中比较常见的有 标记清除(Mark-Sweep)引用计数(Reference Count),而Golang 采用 标记清除法。并在 标记清除法 上使用 三色标记法写屏障 等技术大大提高工作效率。

标记清除收集器是 跟踪式 垃圾收集器,它的工作流程大致分为两个阶段:标记(Mark)清除(Sweep)

  • 标记阶段 — 从 根对象(root) 开始查找并标记 所有存活对象
  • 清除阶段 — 回收 堆中未被标记 的垃圾对象 并 将 回收的内存加入空闲链表

什么是三色标记法

引子

三色标记算法将程序中的对象分成白色黑色灰色三类。

  • 白色:不确定对象
  • 灰色:存活对象,子对象待处理
  • 黑色:存活对象

三色法标记过程

  1. 全部对象加入白色集合(STW)。 01

  2. 根对象 标记为 灰色 加入 灰色集合 02

  3. 垃圾收集器在 灰色集合 逐个取出对象标记为 黑色。而它的指向对象标记为灰色也加入灰色集合 03

  4. 重复 第三步过程,直到灰色集合为空为止 04

  5. 以上4步结束后:需要清理白色对象;保留黑色对象(根可达对象)(STW) 05

并发时三色法存在的问题

由于 三色标记法 多了一个 白色状态 来存放 不确定对象。 结合并发执行的后续标记阶段,就有可能会造成一些遗漏:比如早先被标记为黑色对象 可能目前已经变不可达

并发情况下执行 依然存在问题:GC 过程中对象、指针被改变

比如下面的例子:A (黑) -> B (灰) -> C (白) -> D (白)
通常 D 对象最后加入黑色集合不被回收。

但在并发情况下:A 获得了 D 的引用, 而C 对 D 的引用被用户删除 。
这是 GC并发运行,D 就在也没机会被标记为黑色(A 已经处理,这一轮不会再处理)

A (黑) -> B (灰) -> C (白) 
  ↓
 D (白)

这显然是不允许的。

什么是写屏障

为了解决这个问题,Golang 的垃圾收集器使用了写屏障(Write Barrier)技术:

当对象新增更新时,将对象着色为灰色

写屏障主要做一件事情:修改原来写逻辑,在对象新增更新 同时给它着色为灰色。 在并发情况下写屏障保证三色标记法的正确运行。

如此一来:即使 GC与用户程序并发执行。对象引用发生变化,垃圾收集器也能正确处理。

而写屏障标记成灰色的对象会在 在新的 GC 过程中所有已存对象 又从白色开始 逐步 按上面 三色法标记过程 分类处理

完整的 GC 分为四个阶段:

1)标记准备(Mark Setup,需 STW),打开写屏障(Write Barrier) 2)使用三色标记法标记(Marking, 并发) 3)标记结束(Mark Termination,需 STW),关闭写屏障。 4)清理(Sweeping, 并发)

参考

fullstack 垃圾收集器 三色标记法