GC:Garbage Collection(垃圾回收是编程语言中提供的自动的内存管理机制,自动释放不需要的内存对象,让出存储器资源。)GC过程中无需程序员手动执行,GC机制在现代很多编程语言都支持,GC能力的性能与优劣也是不同语言之间对比度指标之一。

关于Golang的GC在演进过程中经历了多次的变革。具体主要有如下几次重大改变:

  1. Go V1.3的标记-清除(mark and sweep)法。
  2. Go V1.5的三色并发标记法。
  3. Go V1.5的强三色不变式、弱三色不变式、插入屏障、删除屏障。
  4. Go V1.8的混合写屏障机制。

下面我们单独来讲一下Golang GC的几次重大改变。

Go V1.3的标记-清除(mark and sweep)法。


Go V1.3之前标记-清除算法核心步骤:

  1. 启动STW
  2. Mark需要清除的内存对象
  3. Sweep清除需要清除的对象
  4. 停止STW

Go V1.3标记-清除算法核心步骤:

  1. 启动STW
  2. Mark需要清除的内存对象
  3. 停止STW
  4. Sweep清除需要清除的对象

V1.3之前标记-清除算法详细过程:

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

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

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

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

image-20220324084604792

image-20220324084632635

如上图,目前程序的可达对象有1-2-3,4-7,stw之后标记。操作非常简单,但是有一点需要额外注意:mark and sweep算法在执行的时候,需要程序暂停!即 STW(stop the world),STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大,所以STW也是一些回收机制最大的难题和希望优化的点。所以在执行第三步的这段时间,程序会暂定停止任何工作,卡在那等待回收执行完毕。

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

  • STW,stop the world;让程序暂停,程序出现卡顿
  • 标记需要扫描整个heap
  • 清除数据会产生heap碎片

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


Go V1.5的三色并发标记法的核心步骤:

  1. 启动STW
  2. 新创建的对象默认颜色是白色
  3. GC回收从根节点一次遍历所有对象,把遍历到的对象从白色集合放入灰色集合。
  4. 循环遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,直到灰色中无任何对象
  5. 停止STW
  6. 回收所有的白色标记表的对象.也就是回收垃圾。

Go V1.5的三色并发标记法的详细过程:

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

image-20220324084722724

上图所示,我们的程序可抵达的内存对象关系如左图所示,右边的标记表,是用来记录目前每个对象的标记颜色分类。这里面需要注意的是,所谓 “程序”,则是一些对象的根节点集合。所以我们如果将“程序” 展开,会得到类似如下的表现形式,如图所示。

image-20220324084753715

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

image-20220324084811787

这里 要注意的是,本次遍历是一次遍历,非递归形式,是从程序抽次可抵达的对象遍历一层,如上图所示,当前可抵达的对象是对象 1 和对象 4,那么自然本轮遍历结束,对象 1 和对象 4 就会被标记为灰色,灰色标记表就会多出这两个对象。

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

image-20220324084837869

这一次遍历是只扫描灰色对象,将灰色对象的第一层遍历可抵达的对象由白色变为灰色,如:对象 2、对象 7. 而之前的灰色对象 1 和对象 4 则会被标记为黑色,同时由灰色标记表移动到黑色标记表中。

第四步, 重复第三步, 直到灰色中无任何对象,如图所示。

image-20220324084850196

image-20220324084902215

当我们全部的可达对象都遍历完后,灰色标记表将不再存在灰色对象,目前全部内存的数据只有两种颜色,黑色和白色。那么黑色对象就是我们程序逻辑可达(需要的)对象,这些数据是目前支撑程序正常业务运行的,是合法的有用数据,不可删除,白色的对象是全部不可达对象,目前程序逻辑并不依赖他们,那么白色对象就是内存中目前的垃圾数据,需要被清除。

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

image-20220324084930451

以上我们将全部的白色对象进行删除回收,剩下的就是全部依赖的黑色对象。

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

Go V1.5 的强三色不变式、弱三色不变式、插入屏障、删除屏障。(这个是对三色标记法的优化,我这里就不做详细讲述。)

Go V1.8 的混合写屏障机制。

Go V1.8 的混合写屏障机制核心步骤:

  1. 扫描栈中内存对象全部标记黑色,对新增加的对象标记为黑色
  2. 扫描堆中的全部对象,把遍历到的对象从白色集合放入灰色集合。循环遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,直到灰色中无任何对象,对被删除的对象标记为灰色,对被添加的对象标记为灰色。
  3. 回收所有的白色标记表的对象. 也就是回收垃圾。

关于混合写屏障,我们具体就场景进行分析。

GC 开始:扫描栈区,将可达对象全部标记为黑

image-20220324084945148

image-20220324084954492

场景一:对象被一个堆对象删除引用,成为栈对象的下游

image-20220324085014700

image-20220324085022364

场景二:对象被一个栈对象删除引用,成为另一个栈对象的下游

image-20220324085042889

image-20220324085050267

image-20220324085058110

场景三:对象被一个堆对象删除引用,成为另一个堆对象的下游

image-20220324085112108

image-20220324085118814

image-20220324085126319

场景四:对象从一个栈对象删除引用,成为另一个堆对象的下游

image-20220324085138930

image-20220324085145868

image-20220324085153586

混合写屏障的具体核心规则如下:

\1. GC 开始将栈上的对象全部扫描并标记为黑色 (之后不再进行第二次重复扫描,无需 STW),

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

\3. 被删除的对象标记为灰色。

\4. 被添加的对象标记为灰色。

总结:

GoV1.8 三色标记法加混合写屏障机制,栈空间不启动屏障机制,堆空间启动屏障机制。整个过程几乎不需要 STW,效率较高。

image-20220324085210467