go gc总结

15

1.3之前的标记清除算法

第一步,暂停程序业务逻辑, 分类出可达和不可达的对象,然后做上标记。

第二步, 开始标记,程序找出它所有可达的对象,并做上标记

第三步,  标记完了之后,然后开始清除未标记的对象

第四步, 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。

标记-清除(mark and sweep)的缺点

标记清除算法明了,过程鲜明干脆,但是也有非常严重的问题。

  • STW,stop the world;让程序暂停,程序出现卡顿 (重要问题)

  • 标记需要扫描整个heap;

  • 清除数据会产生heap碎片。

Go V1.3版本之前就是以上来实施的,  在执行GC的基本流程就是首先启动STW暂停,然后执行标记,再执行数据回收,最后停止STW

Go V1.3版本做了一个简单优化,将数据回收放在了停止STW之前,将STW的步骤提前, 减少STW暂停的时间范围

三、Go V1.5的三色并发标记法

第一步 , 每次新创建的对象,默认的颜色都是标记为“白色”

第二步, 每次GC回收开始, 会从根节点开始遍历所有对象,把遍历到的对象从白色集合放入“灰色”集合

第三步, 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合

第四步, 重复第三步, 直到灰色中无任何对象

第五步: 回收所有的白色标记表的对象. 也就是回收垃圾

以上便是三色并发标记法,不难看出,我们上面已经清楚的体现三色的特性。但是这里面可能会有很多并发流程均会被扫描,执行并发流程的内存可能相互依赖,为了在GC过程中保证数据的安全,我们在开始三色标记之前就会加上STW,在扫描确定黑白对象之后再放开STW。但是很明显这样的GC扫描的性能实在是太低了。

那么Go是如何解决标记-清除(mark and sweep)算法中的卡顿(stw,stop the world)问题的呢?

有两种情况,在三色标记法中,是不希望被发生的。

  • 条件1: 一个白色对象被黑色对象引用(白色被挂在黑色下)

  • 条件2: 灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)
    如果当以上两个条件同时满足时,就会出现对象丢失现象!

强三色不变式

不存在黑色对象引用到白色对象的指针。

强三色不变色实际上是强制性的不允许黑色对象引用白色对象,这样就不会出现有白色对象被误删的情况。

弱三色不变式

所有被黑色对象引用的白色对象都处于灰色保护状态。

弱三色不变式强调,黑色对象可以引用白色对象,但是这个白色对象必须存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。 这样实则是黑色对象引用白色对象,白色对象处于一个危险被删除的状态,但是上游灰色对象的引用,可以保护该白色对象,使其安全。

为了遵循上述的两个方式,GC算法演进到两种屏障方式,他们“插入屏障”, “删除屏障”。

插入屏障

具体操作: 在A对象引用B对象的时候,B对象被标记为灰色。(将B挂在A下游,B必须被标记为灰色)

满足: 强三色不变式. (不存在黑色对象引用白色对象的情况了, 因为白色会强制变成灰色)

执行过程:

在三色标记过程中,如果出现已经标记的黑色对象引用白色对象的情况,对于堆上的空间,直接将该白色对象改为灰色;

如果在栈空间上,启动STW暂停,在栈空间上重新进行三色标记,直到栈空间的三色标记结束.

最后将栈和堆空间 扫描剩余的全部 白色节点清除.  这次STW大约的时间在10~100ms间.

(3)  删除屏障

具体操作: 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。

满足: 弱三色不变式. (保护灰色对象到白色对象的路径不会断)

执行过程:

在三色标记过程中,假如有对象A->B->C->D,A对象为根对象,标记为灰色。B、C、D均为白色

如果A对象删除了对B对象的引用,则其他三个对象都为白色,有可能在下一轮GC中被删除。

而删除屏障是在A删除对B的引用时,将B对象标记为灰色,此时,在后续的遍历中,BCD都会标记为黑色,从而不会被删除。

这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。

根对象(Root Objects)包括全局变量、栈上的局部变量等。

六、Go V1.8的混合写屏障(hybrid write barrier)机制

插入写屏障和删除写屏障的短板:

  • 插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;

  • 删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

Go V1.8版本引入了混合写屏障机制(hybrid write barrier),避免了对栈re-scan的过程,极大的减少了STW的时间。结合了两者的优点。

核心定义:

GC刚开始的时候,会将栈上的可达对象全部标记为黑色。

GC期间,任何在栈上新创建的对象,均为黑色。

上面两点只有一个目的,将栈上的可达对象全部标黑,最后无需对栈进行STW,就可以保证栈上的对象不会丢失。有人说,一直是黑色的对象,那么不就永远清除不掉了么,这里强调一下,标记为黑色的是可达对象,不可达的对象一直会是白色,直到最后被回收。

堆上被删除的对象标记为灰色

堆上新添加的对象标记为灰色

总结

Golang v1.3之前采用传统采取标记-清除法,需要STW,暂停整个程序的运行。

v1.5版本中,引入了三色标记法和插入写屏障机制,其中插入写屏障机制只在堆内存中生效。但在标记过程中,最后需要对栈进行STW。

v1.8版本中结合删除写屏障机制,推出了混合屏障机制,屏障限制只在堆内存中生效。避免了最后节点对栈进行STW的问题,提升了GC效率

  • go v1.1:标记-清除法,整个过程都需要STW,STW时间可能是秒级别

  • go v1.3:标记-清除法,标记过程仍然需要STW但清除过程并行化(Mark和Sweep分离. Mark STW, Sweep并发),STW几百ms

  • go v1.4:runtime代码基本都由C和少量汇编改为Go和少量汇编, 包括GC部分, 以此实现了准确式GC,减少了堆大小, 同时对指针的写入引入了写屏障, 为1.5铺垫 STW几百ms

  • go v1.5:引入插入写屏障技术的三色标记法,仅在堆空间启动插入写屏障,全部扫描后需要STW重新扫描栈空间,并发Mark, 并发Sweep,STW耗时降到10-40ms

  • go v1.6:v1.5中一些与并发GC不协调的地方更改. 集中式的GC协调协程, 改为状态机实现 STW耗时降到5-20ms

  • go v1.7:GC时栈收缩改为并发, span中对象分配状态由freelist改为bitmap STW耗时降到1-3ms左右

  • go v1.8:引入混合写屏障技术的三色标记法,仅在堆空间启动混合写屏障,不需要在GC结束后对栈空间重新扫描,STW耗时降到0.5ms左右

  • go v1.14:引入新的页分配器用于优化内存分配的速度,STW非常短。

参考资料:

https://www.yuque.com/aceld/golang/zhzanb

https://www.cnblogs.com/cxy2020/p/16321884.html

https://baijiahao.baidu.com/s?id=1703897499120812551&wfr=spider&for=pc