什么是GC?他的英文全称(英语:Garbage Collection,缩写为GC),在计算机科学中是一种自动的存储器管理机制。当一个计算机上的动态存储器不再需要时,就应该予以释放,以让出存储器,这种存储器资源管理,称为垃圾回收。垃圾回收器可以让程序员减轻许多负担,也减少程序员犯错的机会(-----来自维基百科)
简单地说,垃圾回收(GC)是在后台运行一个守护线程,它的作用是在监控各个对象的状态,识别并且丢弃不再使用的对象来释放和重用资源。本文主要用来介绍这个Go语言当中的gc机制其它的语言的gc机制博主并不了解,如果有兴趣的可以自行百度。当前Golang使用的垃圾回收机制是三色标记发配合写屏障和辅助GC,三色标记法是标记-清除法的一种增强版本。下面我们一一来介绍以下这几个版本的不同。
Go V1.3之前的标记-清除(mark and sweep)算法。下面我们一起来看看标记清除法是什么?这个算法主要有两个步骤:
1.标记。 2.清除
该算法首先需要暂停程序的业务逻辑,找到不可达对象和可达对象,开始标记将所有的可达对象做上标记,标记完成之后然后将未标记的对象清除。最后回复程序的业务逻辑也就是让程序跑起来。重复这个过程直到程序的生命周期结束。下面我们来看看其大致流程

下面通过几张图带大家熟悉一下这个过程

其中这个箭头的指向代码一个对象引用了另外一个对象。比如对象1和对象2之间存在对象1到对象2的箭头这就表示对象1引用了对象2.
下面我们来过一遍这个流程是怎么样的。首先我们暂停业务逻辑, 找出不可达的对象,然后做上标记。第二步,回收标记好的对象。第一步这个很容易直接遍历即可,但是需要暂停业务逻辑此时程序会被卡住。
开始标记,程序找出它所有可达的对象,并做上标记。如下图所示:

标记完了之后,然后开始清除未标记的对象. 结果如下

第四步:停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束
这种方式有一个致命的缺点就是需要将程序暂停,程序出现卡顿 (重要问题)。还有一些小问题就是这个:标记需要扫描整个heap,清除数据会产生heap碎片。
三色标记法的出现主要是为了减少这个STW时间或者不使用STW时间。下面我们一起来了解一下什么是三次标记法。
三色标记法 实际上就是通过三个阶段的标记来确定清楚的对象都有哪些,因此在三色标记法当中有三种颜色。下面分别解释一下这三种颜色分别代表什么意思:
我们来看一下具体的过程.
第一步首先新创建的对象颜色都是白色的并且会被放入白色集合当中,如下图所示

这个非常的简单,但是把所有的白色对象放入到白色集合这个过程是需要STW的也就是暂停程序。
这里面需要注意的是, 所谓“程序”, 则是一些对象的跟节点集合.

在这里解释一下什么叫做跟节点集合:
那么什么是root呢?root区域主要是程序运行到当前时刻的栈和全局数据区域。这点是需要注意的需要搞明白
第二步每次GC的时候从根节点集合开始遍历可达节点,并把遍历到的白色对象从白色集合当中放入到灰色集合当中。

第三步遍历灰色集合,将灰色集合当中的对象引用的白色对象放入到灰色集合当中。放入完毕之后将灰色对象放入到黑色集合当中。

第四步也就是重复第三步直到灰色集合当中无对象

第五步回收无用对象,也就是剩下的白色对象

以上就是三色遍历法的全部过程,有点这个宽度优先遍历的感觉(类似二叉树的层序遍历).下面总结一下
1.初始时,所有对象都在 【白色集合】中;(搜索白色集合需要STW)
2.将GC Roots 直接引用到的对象 挪到 【灰色集合】中。
3.从灰色集合中获取对象:
3.1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中。
3.2. 将本对象 挪到 【黑色集合】里面。
4.重复步骤3,直至【灰色集合】为空时结束。
5.结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收。

下面我们已经明白了三次标记法的大致流程和思路,那么这样的三色标记法就可以步使用STW(暂停程序)了吗?下面我们来看看这个问题
我们还是基于上述的三色并发标记法来说, 他是一定要依赖STW的. 因为如果不暂停程序, 程序的逻辑改变对象引用关系, 这种动作如果在标记阶段做了修改,会影响标记结果的正确性。我们举一个场景.
如果三色标记法, 标记过程不使用STW将会发生什么事情?

以标记为灰色的对象2有指针指向对象3,或者我们说引用了对象3.如果我们不在STW那么此时在这种情况下就可能出现问题了
此时在进行GC但是程序也是在运行的,此时黑色对象4创建了一个指针指向了白色对象3。并且这个黑色对象2删除了和对象3的引用关系


那么此时最后的结果就会变为:

此时对象3被当成垃圾被回收了,这是我们不允许出现的。通过了上述流程的分析我们发现这种情况是三色标记法最不希望发生的:
如果这两种情况同时发生那么白色对象就可能会被当做垃圾被回收掉,那么这个问题该如何解决呢?最简单的办法就是才有STW暂停程序。但是STW又会影响这个程序的效率,如何能在保证对象不丢失的情况下合理的尽可能的提高GC效率,减少STW时间呢?
为了解决这个问题谷歌团体引入了两个屏障机制来解决这个问题
通过上面的分析我们发现如果上面这两种情况同时满足就会导致对象被当中垃圾回收掉。如果我们破坏掉一个条件是不是就可以了。
因此谷歌团队引入了强弱三色不变式:
首先我们来看看这个强三色不变式:不允许黑色对象引用白色对象

其次是这个弱三次不变式:所有被黑色对象引用的白色对象都处于灰色保护状态.

通过这两种方式谷歌团队引入了这个插入屏障和删除屏障
首先我们来看看这个具体的操作是如何做的: 在A对象引用B对象的时候,B对象被标记为灰色(如果对象B是白色)
满足: 强三色不变式. (不存在黑色对象引用白色对象的情况了, 因为白色会强制变成灰色)
下面我们来举一个例子:
A之前没有引用任何对象,现在让其引用一个对象B(对象B会被置为黑色)。还有一种情况就是
A之前引用了这个对象C,现在A想引用B也就是不引用C了此时B也会被置为灰色。
下面我们通过几张图来看看这个这个过程的大致是个什么情况

上面的过程和三次标记法一样一开始需要将白色对象放到白色集合当中。在这里特别需要注意的是在这里栈是不开启插入写屏障的为了保证栈的效率


由于我们没有这个暂停程序,因此黑色对象引用了这个白色对象8,那么此时开启这个插入屏障,白色对象会被置为灰色。由于这个栈是不开启这个插入写屏障的所有对象9并不会被标记为灰色

然后继续三次标记法的流程,遍历完成后的样子大致是这样

但是可能就有老铁说了此时对象9不会被当作是垃圾而被清除掉吗?此时在处理回收这个白色对象时,会开启STW注意只是对栈开启这个SWT将栈里面的对象全部置为白色重新上述过程。这样就能保证这个对象9不会被误删

最后结果应该是这样的

然后将白色对象清理完毕之后就可以暂停这个SWT了。但是这种方法还是需要暂停这个栈区没有完全的摆脱SWT.后面总结时在详细说明
删除写屏障是指:被删除引用关系的对象,如果自身为灰色或者白色,那么被标记为灰色。这一点其实就是为了满足弱三色不变式。
满足: 弱三色不变式. (保护灰色对象到白色对象的路径不会断)
下面我们举个例子来说明这种情况
比如A 此时引用了B对象,但是此时A要删除和对象B的引用关系此时。此时如果B对象是白色对象那么B对象会被置为灰色。还有一种情况就是
A对象之前引用了B对象此时,此时A更换了引用关系引用C也就是A对象和B对象之间的引用关系被删除掉了。此时B对象会被置为灰色。
下面我们来通过几张图来看看这个删除写屏障。

首先第一步还是一样的按照三色标记法的步骤来将所有的白色节点放入到栈中,这一步和三色标记法是一样的。然后我们继续这个上述流程

此时对象1想要删除和对象5之间的引用关系,此时就会触发删除写屏障。

然后继续三色标记法的步骤也是一样的。

最后的情况就是

可能有老铁要说了,对象5和对象2和对象3不是垃圾吗?不应该被回收吗。为什么保留下来了?是的在这一次GC过程当中他们确实是被保留下来了,但是下一轮GC的时候就会被干掉了。
总结:
1.这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。
2.需要SWT暂停这个栈包含栈在次GC,虽然栈里面的对象数量不是很多但是 仍然需要SWT。在一定程度上还是会影响效率。
混合写屏障机制的引入主要是为了解决这个三色标记法当中需要第给栈区使用SWT暂停栈,重复进行一次GC的缺点。
下面我们在这里总结一下插入写屏障和删除写屏障的缺点。然后再来看看这个混合写屏障机制
Go V1.8版本引入了混合写屏障机制(hybrid write barrier),避免了对栈re-scan的过程,极大的减少了STW的时间。结合了两者的优点。下面我们一起来了解一下这个机制是怎么回事。
其主要为了满足弱三色不变式。注意混合写屏障是Gc的一种屏障机制,所以只是当程序执行GC的时候,才会触发这种机制。
下面我们来分析这种方法的可行性。

请注意GC刚开始所有对象都是白色的,然后第一步我们扫描栈区将栈上的对象全部标记为黑色

下面我们看看以下四种常见,在混合写屏障的机制下
情况一:对象被一个堆对象删除引用,成为栈对象的下游

对象1添加引用关系到对象7此时因为栈是不开启任何屏障机制所以了直接添加即可。此时对象4删除和对象7的引用关系。

此时对象4删除了和对象7的引用关系那么按照规则我们需要将对象7置为灰色。此时我们发现不会有任何错误
情况二:对象被一个栈对象删除引用,成为另一个栈对象的下游

首先在栈上新键一个对象9,按照规则在栈上创建的对象均为黑色对象。

添加对象9到对象3的引用关系由于这个栈不启动任何屏障直接添加即可。

接着对象2删除和对象3之间的引用关系,由于是在栈上所以了之间删除即可。此时也是没有任何问题,不存在误删的情况
情况三:对象被一个堆对象删除引用,成为另一个堆对象的下游

对象10想要添加这个引用关系和对象7

由于是在堆上所以了会触发这个屏障机制会将对象7变为灰色。对象7变为灰色之后,就不会被当中垃圾被回收了。
情况四:对象从一个栈对象删除引用,成为另一个堆对象的下游

对象1想要删除和对象2之间的引用关系由于栈不开启这个屏障机制所以直接删除即可。

对象4尝试引用对象2,对象2本来就是黑色了所有不用管。

对象1删除和对象2之间的引用关系直接删了就可以,对象4删除和对象7之间的引用关系此时由于是在堆上所以了触发了这个混合写屏障机制将对象7置为灰色。
可能有老铁就要问了对象7和对象6现在不是属于垃圾对象了吗?应该被回收但是对象7是灰色,这会让对象7和对象6不被回收掉。是的在这一轮GC的情况下确实不会被回收,但是下一轮GC就会将对象7和对象6回收掉。这一点设计者是考虑到了的
总结:
Golang中的混合写屏障满足
弱三色不变式,结合了删除写屏障和插入写屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间。
以上就是Go语言垃圾回收机制