go语言gc
介绍
go语言的垃圾回收是自动实现的,所谓垃圾回收就是释放哪些不会再使用的程序所占的空间,比如已
经没有引用的变量。垃圾回收过程是与go程序并发执行的。目前go语言v1.8使用的是混合写屏障,在
此之前使用的是Mark and Sweep算法,v1.5是三色标记法。本笔记针对三色标记发经行学习。
Mark and Sweep
Mark and Sweep主要是两个步骤:
1.标记(Mark phase)
2.清除(Sweep phase)
第一步,找出不可达的对象,然后做上标记。
第二步,回收标记好的对象。
操作非常简单,但是有一点需要额外注意:mark and sweep算法在执行的时候,需要程序暂停!即STW(stop the w orld)。
也就是说,这段时间程序会卡在哪儿。在暂停期间,从根对象开始标记扫描
整个堆,找到所有可达到的对象并做上标记,然后删除所有未标记对象,达
到垃圾回收功能。
缺点
垃圾回收时会暂停整个程序,并且需要扫描整个堆。会影响程序执行效率。
三色标记法
在go中,这个问题是不太可以接受的,所以go后续采用三色标记算法来经行
垃圾回收。
三色标记算法的原则就是把堆中的对象分配到不同颜色的集合当中,通过
黑,白,灰三色来标记对象。其中各颜色的含义是:
黑:对象和它所直接引用的所有对象都被访问过。
灰:这个对象已经被访问过,但是这个对象所直接引用的对象中,至少还有
一个没有被访问到,表示这个对象正在枚举中。
白:这个对象还没有被访问过,在初始阶段,所有对象都是白色,所有都枚
举完仍是白色的对象将会被当做垃圾对象被清理。
根据这些定义,我们可以得出:
1.在可达性分析的初始阶段,所有对象都是白色,一旦访问了这个对象,那
么就变成灰色,一旦这个对象所有直接引用的对象都访问过(或者没有引用
其它对象),那么就变成黑色
2.初始标记之后,GC Root节点变为黑色(GC Root不会是垃圾),GC Root
直接引用的对象变为灰色
3.正常情况下,一个对象如果是黑色,那么其直接引用的对象要么是黑色,
要么是灰色,不可能是白色(如果出现了黑色对象直接引用白色对象的情
况,就说明漏标了,就会导致对象误删,后面会介绍如何解决),这个特性
也可以说是三色标记算法正确性保障的前提条件。
算法大致的流程是(初始状态所有对象都是白色):
1.首先我们从GC Roots开始枚举,它们所有的直接引用变为灰色,自己变为
黑色。可以想象有一个队列用于存储灰色对象,会把这些灰色对象放到这个
队列中。
2.然后从队列中取出一个灰色对象进行分析:将这个对象所有的直接引用变
为灰色,放入队列中,然后这个对象变为黑色;如果取出的这个灰色对象没
有直接引用,那么直接变成黑色。
3.继续从队列中取出一个灰色对象进行分析,分析步骤和第二步相同,一直
重复直到灰色队列为空。
4.分析完成后仍然是白色的对象就是不可达的对象,可以作为垃圾被清理。
5.最后重置标记状态。
并发标记可能出现的问题
1.本来为垃圾的对象变为非垃圾。
此现象为产生了浮动垃圾,即原被标记为黑色的对象,在用户操作中失去了
与父节点的连接,成为了垃圾,但是又被标为了黑灰色,则无法在最后被回
收。
该现象会占用不必要的资源,但是在下一轮gc操作后,会被清除,不会造成
太大影响。
2.本来不是垃圾的对象变为垃圾。
在用户态执行的过程中,可能会将原本灰色结点引用的对象,变改为黑色结
点去引用,此时会产生一个已经被标记为黑色对象下还引用一个白色对象的
现象。此现象会产生很大的bug,即在gc回收时,会将白色对象清除,原本
应该被引用的对象也会被清除,导致程序无法正常执行。
解决办法
为解决此问题,增加了读写屏障与增量更新与原始快照(SATB)三个方法
读写屏障
这里的屏障很简单,可以理解成就是在读写操作前后插入一段代码,用于记
录一些信息、保存某些数据等,概念类似于AOP。
增量更新
增量更新则是当黑色对象新增一个白色对象的引用时,就通过写屏障将这个
引用关系记录下来。然后在重新标记阶段,再以这些引用关系中的黑色对象
为根,再扫描一次,以此保证不会漏标。
原始快照
原始快照则是针对被减少引用的对象经行的操作,在该对象减少引用前,进
行写屏障操作,将被减少的对象记录下来,再断开之间的引用,后续将被记
录的对象标记为黑色。这样会造成两个后果,被记录的对象是垃圾,需要回
收,此时就造成了浮动垃圾,在下一轮gc中会被清理,如果被记录对象不是
垃圾,则就避免了bug的产生。
优缺点
增量更新可以有效避免过多的浮动垃圾的产生,但是原始快照效率更高,不
需要再对原有的结点经行枚举,减少了多余操作,增量更新操作在重新标记
时需要STW,原始快照的实现也更加的简单方便。在并发清理阶段或者不是
状态转移,而是直接创建的新对象被黑色对象引用时,就无法经行原始快照
操作,解决办法除了使用增量更新以外,也可以将新创建的对象直接标记为
黑色,缺点虽然与原始快照一样会产生浮动垃圾,但是加快了速率,也更好
实现。