文章目录
一.历史原因
go发展史上采用过的一些方法
go 1.3
之前使用标记清除go 1.5
三色标记法go 1.8
三色标记法 + 混合写屏障机制
垃圾回收设计的名词
- 自动释放
- 垃圾回收
- 三色标记法
- 内存管理
- SWT(stop the world) 全部停止执行
v1.3 之前的标记 - 清除方法
暂停程序业务逻辑,找出不可达的对象,和可达对象。
找到所有的可达对象,并做上标记。
标记完成以后,开始清楚未标记的对象
停止暂停,让程序继续跑,然后循环重复这个过程,直到 process 程序生命周期结束。
标记清除的缺点
- STW:让程序暂停,程序出现卡顿(重要问题)
- 标记需要扫描整个
heap
- 清除数据会产生
heap
碎片 - 将第三步和第四步调换位置,缩短
STW
的返回,但是还是会很大。
二.三色标记法
三色标记法的流程
只要是新创建的对象,默认颜色就是白色
第二步,每次 GC 回收开始,然后从根节点开始遍历所有对象,把遍历到的对象,从白色集合放入灰色集合
遍历灰色集合,将灰色对象的引用对象,从白色集合放入灰色集合,之后会将此灰色对象放入到黑色集合。
遍历上边的步骤,直到灰色节点里没有东西。
如果三色标记法不使用 STW 会出现的问题
如果三色标记法不被 STW 保护
- 一个白色对象被黑色对象引用
- 灰色对象与它之间的可达关系的白色对象遭到破坏
- 如果两个条件同时满足,那么就会出现对象丢失的现象
强弱三色不变式
强制性的不允许黑色对象引用白色对象
:破坏了条件一黑色对象可以引用白色对象,但是要求白色对象的链路上游,存在灰色对象
:破坏了条件二
只要满足了强三色不变式或者弱三色不变式之一,就可以保证对象不丢失。那么如何保证呢?就要借助于屏障机制。
三.屏障机制
屏障机制有点类似于其他语言的钩子函数,可以在不影响正常主程序正常使用的情况下,做一下额外的事情。有点类似于 vue
里面的声明周期函数
插入屏障:
对象被引用时触发的机制删除屏障:
对象被删除时,触发的机制
插入屏障
具体操作: 将 B 挂载到 A 下游,B 必须被标记为灰色
满足了强三色不变式,不存在黑色对象引用白色对象的情况,因为白色会被强制编程灰色
注意,插入屏障,只是对 堆
内的数据启动插入屏障,因为 栈
的内存比较小,所以 栈
不启动插入屏障,所以在回收白色节点之前,会对栈
中的数据重新扫描,并且加 STW
暂停保护栈,防止外界干扰。然后对 栈
中的对象进行一次三色标记,直到没有灰色节点。
插入屏障存在的不足:
结束时,需要STW来重新扫描栈,大约需要10~100ms
删除屏障
被删除对象,如果自身为灰色或者白色,那么就被标记为灰色。
满足 弱三色不变式(保护灰色对象到白色对象的路径不会断)
遍历灰色的标记,将可达对象,从白色标记为灰色,遍历灰色以后,就会将灰色标记黑色,
由上图可以看出,这种方法存在的问题
回收精度低,一个对象即使被删除了最后一个指向它的指针,也依然可以活过这一轮,在下一轮 GC
中被清理掉。
它之所以要这么做,就是为了防止在删除的时候,成为另一个对象的引用节点。
四. 三色标记法 + 混合写屏障
GC
开始将栈上的可达对象全部扫描,并标记为黑色(这是一个前提,之后不再进行第二次重复扫描,无需STW
)GC
期间,在栈上创建的新对象,均为黑色- 被删除的对象标记为灰色
- 被添加的对象标记为灰色
栈不启用屏障,堆启用屏障
满足: 变形的弱三色不变式,(结合了插入、删除写屏障两者的优点)
四 总结
- Go V1.3 中普通的标记清除法,整体过程需要 STW,效率极低
- G0 V1.5 三色标记法,对空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通
- 三色标记法 + 混合写屏障机制,栈空间不启动,堆空间启动,整体过程几乎不需要 STW,效率极高。
查考文献
【1】https://www.bilibili.com/video/BV1wz4y1y7Kd?p=14&share_source=copy_web