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