这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
1、Go V1.3之前的标记清除(mark and sweep)
此算法主要有两个主要的步骤:
- 标记(Mark phase)
- 清除(Sweep phase)
第一步,暂停程序业务逻辑(STW), 分类出可达和不可达的对象,然后做上标记。
第二步, 开始标记,程序找出它所有可达的对象,并做上标记。如下图所示:
第三步, 标记完了之后,然后开始清除未标记的对象. 结果如下。
注意:需要程序暂停!即 STW(stop the world),STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大,所以STW也是一些回收机制最大的难题和希望优化的点。所以在执行第三步的这段时间,程序会暂定停止任何工作,卡在那等待回收执行完毕。
第四步, 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。
缺点
- STW,让程序暂停,程序出现卡顿
- 标记需要扫描整个heap
- 清除数据会尝试heap碎片(地址不相连)
2、Go V1.5三色标记法
三色:write,grey,black。
白色:没有遍历到
黑色:已经遍历到了
灰色:可遍历,还没有遍历。中间态
第一步:所有的节点都为白色
第二步, 每次GC回收开始, 会从根节点开始遍历所有对象,把遍历到的对象从白色集合放入“灰色”集合如图所示。
第三步, 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,如图所示。
第四步, 重复第三步, 直到灰色中无任何对象,如图所示。
第五步: 回收所有的白色标记表的对象. 也就是回收垃圾,如图所示。
没有STW的三色标记法
在三色标记法中,是不希望被发生的。
- 条件1: 一个白色对象被黑色对象引用 (白色被挂在黑色下)
- 条件2: 灰色对象与它之间的可达关系的白色对象遭到破坏 (灰色同时丢了该白色)
如果当以上两个条件同时满足时,就会出现对象丢失现象!
屏障机制
(1) “强-弱” 三色不变式
强三色不变式: 强制性不允许黑色对象引用到白色对象的指针。
弱三色不变式: 所有被黑色对象引用的白色对象都处于灰色保护状态。
(2) 插入屏障
具体操作: 在A对象引用B对象的时候,B对象被标记为灰色。(将B挂在A下游,B必须被标记为灰色)
满足: 强三色不变式. (不存在黑色对象引用白色对象的情况了, 因为白色会强制变成灰色)
栈空间的特点是容量小,但是要求相应速度快,因为函数调用弹出频繁使用, 所以“插入屏障”机制,在栈空间的对象操作中不使用. 而仅仅使用在堆空间对象的操作中.
不足:结束的时候需要STW来重新扫描栈,需要10~100ms
(3) 删除屏障
具体操作: 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。
满足: 弱三色不变式. (保护灰色对象到白色对象的路径不会断)
不足:回收进度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过当前这一轮,在下一轮GC中被清理掉
3、Go V1.8混合写屏障机制
具体操作:
- GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW),
- GC期间,任何在 栈 上创建的 新对象 ,均为黑色。
- 在堆上被删除的对象标记为灰色(保护删除对象的下游)。
- 在堆上被添加的对象标记为灰色(避免前对象为黑色导致无法扫描添加的对象)。
- 混合写屏障机制导致很多对象在下一轮被删除
场景一:对象被一个堆对象删除了,成为了栈对象的下游
GC调优
- 控制内存分配的速度,限制 Goroutine 的数量,提高赋值器mutat的CPU利用率(降低GC的CPU的利用率)
- 少量使用+连接string(这个过程会从头遍历字符串,旧值需要GC进行一个回收)
- slice提前分配足够的内存来降低扩容带来的拷贝
- 避免map key对象过多,导致扫描时间增加(确实key很多,可以使用byte数组)
- 变量复用,减少对象分配,例如使用 sync.Pool 来复用需要频繁创建临时对象、使用全局变量等
- 增大 GOGC 的值,降低 GC 的运行频率
总结
GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。
GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通
GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。