Golang 垃圾回收(GC)原理
如果❤️我的文章有帮助,欢迎点赞、关注。这是对我继续技术创作最大的鼓励。更多往期文章在我的个人博客
什么是垃圾回收(GC)
垃圾回收算法 中比较常见的有 标记清除(Mark-Sweep) 和 引用计数(Reference Count),而Golang 采用 标记清除法。并在 标记清除法 上使用 三色标记法 和 写屏障 等技术大大提高工作效率。
标记清除收集器是 跟踪式 垃圾收集器,它的工作流程大致分为两个阶段:标记(Mark) 和 清除(Sweep):
- 标记阶段 — 从 根对象(root)开始查找并标记堆中所有存活对象
- 清除阶段 — 回收 堆中未被标记的垃圾对象 并 将回收的内存加入空闲链表
什么是三色标记法
三色标记算法将程序中的对象分成白色、黑色、灰色三类。
- 白色:不确定对象
- 灰色:存活对象,子对象待处理
- 黑色:存活对象
三色法标记过程
- 
    全部对象加入白色集合( STW)。
- 
    将 根对象标记为灰色加入灰色集合
- 
    垃圾收集器在 灰色集合逐个取出对象标记为黑色。而它的指向对象标记为灰色也加入灰色集合
- 
    重复 第三步过程,直到灰色集合为空为止
- 
    以上4步结束后:需要 清理白色对象;保留黑色对象(根可达对象)(STW)
并发时三色法存在的问题
由于 三色标记法 多了一个 白色状态 来存放 不确定对象。
结合并发执行的后续标记阶段,就有可能会造成一些遗漏:比如早先被标记为黑色对象 可能目前已经变不可达。
但 并发情况下执行 依然存在问题: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, 并发)
