1. 标记清除法 (V1.3 版)
1.1. 第一步,暂停程序业务逻辑, 分类出可达和不可达的对象,然后做上标记。
图中表示是程序与对象的可达关系,目前程序的可达对象有对象1-2-3,对象4-7等五个对象。
1.2. 第二步, 开始标记,程序找出它所有可达的对象,并做上标记。
所以对象1-2-3、对象4-7等五个对象被做上标记。
1.3. 第三步, 标记完了之后,然后开始清除未标记的对象.
1.4. 第四步, 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。
以上便是标记-清除(mark and sweep)回收的算法。
1.5. 核心缺陷
mark and sweep 算法在执行的时候,需要程序暂停!即 STW(stop the world),STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大 .
2. 三色标记法 (V1.5 版)
为了优化标记清除法 , go 引进了三色标记法 , 所谓三色标记法实际上就是通过三个阶段的标记来确定清楚的对象都有哪些
三色
- 黑色:已处理且存活的节点
- 白色:未被标记的待回收节点
- 灰色:待处理的中间状态节点
问题 :
为了在GC过程中保证数据的安全,我们在开始三色标记之前就会加上 STW,在扫描确定黑白对象之后再放开 STW。但是很明显这样的GC扫描的性能实在是太低了。
具体步骤如下 :
有点类似树的层序遍历
2.1.1. 第一步 : 每次新创建的对象,默认的颜色都是标记为“白色”
程序可抵达的内存对象关系如左图所示 , 右边的标记表,是用来记录目前每个对象的标记颜色分类 , 分别对应三种颜色。
2.1.2. 第二步: 每次GC回收开始, 会从根节点开始遍历所有对象,把遍历到的对象从白色集合放入“灰色”
本次遍历是一次遍历,非递归形式,是从程序抽次可抵达的对象遍历一层
2.1.3. 第三步 : 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,
2.1.4. 第四步: 重复第三步, 直到灰色中无任何对象。
2.1.5. 第五步: 回收所有的白色标记表的对象. 也就是回收垃圾,
3. 屏障机制
为了解决上面三色标记法存在的 STW问题 , 我们引入屏障机制
垃圾回收过程中的核心机制 : 一个对象所添加的另一个对象必须在 GC Roots可达链路上
3.1. 强弱三色不变式
我们让GC回收器,满足下面两种情况之一时,即可保对象不丢失。 这两种方式就是“强三色不变式”和“弱三色不变式”。
- 强三色不变式
核心目标 : 不存在黑色对象引用到白色对象的指针。
实现方式 : 插入写屏障
- 强三色不变色实际上是强制性的不允许黑色对象引用白色对象,这样就不会出现有白色对象被误删的情况。
- 弱三色不变式
核心目标 : 所有被黑色对象引用的白色对象都处于灰色保护状态。
实现方式 : 删除写屏障
- 黑色对象可以引用白色对象,但是这个白色对象必须存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。
3.2. 插入写屏障
具体操作: 在A对象引用B对象的时候,B对象被标记为灰色。(将B挂在A下游,B必须被标记为灰色)
注意: 不在栈空间使用 , 一方面栈比较小 , 另一方面 , 栈的性能要求比较高 ,该屏障会影响性能
满足: 强三色不变式. (不存在黑色对象引用白色对象的情况了, 因为白色会强制变成灰色)
场景:
A.添加下游对象(nil, B) //A 之前没有下游, 新添加一个下游对象B, B被标记为灰色
A.添加下游对象(C, B) //A 将下游对象C 更换为B, B被标记为灰色
黑色对象的内存槽有两种位置, 栈和堆. 栈空间的特点是容量小,但是要求相应速度快,因为函数调用弹出频繁使用, 所以“插入屏障”机制,在栈空间的对象操作中不使用. 而仅仅使用在 堆空间 对象的操作中.
而对于栈空间, 在扫描操作结束后 , 我们仍然需要使用 STW暂停来重新三色标记扫描, 但这次为了对象不丢失, 要对本次标记扫描启动 STW 暂停. 直到栈空间的三色标记结束.
3.3. 删除写屏障
具体操作: 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。
也有微小的 stw 保存初始状态的快照 , 方便清除时对比
满足: 弱三色不变式. (保护灰色对象到白色对象的路径不会断)
场景:
A.添加下游对象(B, nil) //A对象,删除B对象的引用。 B被A删除,被标记为灰(如果B之前为白)
A.添加下游对象(B, C) //A对象,更换下游B变成C。 B被A删除,被标记为灰(如果B之前为白)
这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。
3.4. 混合写屏障 (V1.8 版)
我们可以通过混合写屏障结合前两种的优点 , 保证没有stw的同时 , 尽可能的提高回收精度
步骤 :
- GC开始将栈上的对象全部扫描并标记为黑色 ( 无需二次扫描和 STW )
- GC期间,任何在栈上创建的新对象,均为黑色。( 注意是创建新对象 ,而不是添加 )
- 堆上被删除的对象标记为灰色。( 仍存在回收精度问题 )
- 堆上被添加的对象标记为灰色。
满足 : 变形的弱三色不变式.
总结 : 混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。
参考视频 : b站刘丹冰老师