【golang】GC的更迭之路

GC三色标记法,读写混合屏障

什么是GC?

GC全称为:garbage collection,即垃圾回收,是一种自动管理内存的机制。

当程序向操作系统申请的内存不再被使用的时候,垃圾回收机制会将这个内存进行回收以便于下次申请内存时复用,或者归还给操作系统。这种内存级别资源的回收操作就是垃圾回收,而负责回收的程序组件就被叫做垃圾回收器。

GC 回收策略

传统的标记清除算法

传统的标记清除只有两种颜色,要么选中要么不选中。如果没选中就会被清除。

在此标记之前会出现STW,如果多次进行标记,那么就需要多次的STW,因此会造成频繁的暂停。下图将会介绍整个GC流程。

【golang】GC的更迭之路_第1张图片

  1. 暂停业务逻辑(STW),找出所有的可达对象以及不可达对象
    【golang】GC的更迭之路_第2张图片

  2. 将所有的可达对象进行标记处理(Mark),什么是可达对象?就是通过系统根节点对象进行访问,能够被访问到的就是可达对象,如果访问不到,说明这个对象应当被回收再次利用!

【golang】GC的更迭之路_第3张图片

  1. 清除所有未被标记的对象(Sweep)。

【golang】GC的更迭之路_第4张图片

  1. 解除暂停(STW),让程序继续运行。然后循环重复以上过程,直至程序结束。

标记清除法的缺点

  1. STW时间问题:STW就是stop the world 字面意思,所有程序暂停执行。因此程序必然会出现卡顿的情况。
  2. 标记过程时间问题:在进行标记的时候需要遍历堆内存的根节点以及这些根节点的后继结点。如果情况比较复杂的时间消耗就会更多。
  3. 碎片化严重:在sweep的过程中,可能有内存地址不连续的对象,将这些对象单独删除后,会产生大小不等的内存块。

最严重的问题就是STW,因为STW是四个步骤进行时间的总和,每次暂停程序都会占用一定的时间,影响程序性能。

优化思路

既然STW会影响到时间,增加消耗,那么我们能不能把这段暂停时间缩短甚至让他变为零呢?

我们能发现Sweep也包含在了STW中,而这段时间并不会影响程序的正常进行。因此可不可以以在第二个步骤进行之后就结束STW呢?。

三色标记法

设置三种颜色:

白色:未确定对象的集合。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均为不可达对象。

灰色:正在访问到的可达对象的集合,需要对其后继检查是否有可达对象,因此暂时标记为灰色。

黑色:已经被访问完成的可达对象的集合。

执行流程

  1. 将所有的对象都设置为白色。

【golang】GC的更迭之路_第5张图片

  1. 优先将根结点对象标记为灰色,表示正在遍历该结点并且该结点可以达到。

【golang】GC的更迭之路_第6张图片

  1. 逐层访问通过灰色结点能够达到的后继结点,并将已经访问过的结点标记为黑色,正在访问的可达结点标记为灰色。重复进行该操作,直到只存在黑色和白色结点为止。

【golang】GC的更迭之路_第7张图片

  1. 访问完成后(只剩下黑色结点和白色结点后),将所有白色结点进行清除(Sweep)。回收再利用。【golang】GC的更迭之路_第8张图片

根对象包括:

  • 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
  • 执行栈:每个goroutine都包含自己的执行栈,这些执行栈上包含栈的变量以及指向已经分配过的堆内存区块的指针。
  • 寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块。

这一标记过程类似于广度优先遍历,逐层标记,并不是递归进行。并且这段过程并没有开启STW,那么会造成什么后果呢?

不开启STW进行保护的问题(程序与GC并行进行)

假设在重复第一步至第三步逐层访问的过程中,程序也在不断地运行。

当有一个灰色结点正在被访问的过程中,这个灰色结点有一个后继结点等待被下一轮访问。这时程序将这个指针给删除了,也就是说灰色结点不再指向这个后继结点。

此时程序又将一个黑色已经被访问完毕的结点指向了这个灰色结点的后继结点。当GC执行完毕之后所有白色结点将被回收。

那么问题也发生了,在访问过程中遗漏了经过更改的对象结点。

解决访问遗漏出现的思路

  • 强三色不变式

    • 强制要求黑色结点不能指向白色结点
  • 弱三色不变式

    • 黑色结点可以指向白色结点,但前提是该白色的上游链路中有一个灰色结点所指向。

只要出现强三色/弱三色不变式两个条件之一,就可以解决该问题。

三色标记法和屏障机制

插入屏障

对堆空间施行插入屏障。对栈进行STW保护。

当对象A引用B对象的时候,B对象直接设置为灰色,表示等待访问。也就是说B对象为新插入对象,只要插入扫描程序中,就直接被赋予为灰色。

这样操作满足了强三色不变式,黑色永远不会指向白色结点。

插入屏障的缺点

只有堆进行了插入屏障,而栈不会进行插入屏障。当GC结束后会重新开启STW单独对栈进行重新扫描,并且再次过程中不允许外界进行干扰。这仍然使用了STW,也降低了小部分性能。

删除屏障

当对象要被删除时,需要判断对象为什么颜色,如果对象为灰色或者白色,则全部标记为灰色。

那么这个对象这一轮不会被删除掉。但是指向它的那个指针在本轮已经被删除掉,那么他将会在下一轮被GC回收。

这样操作满足了弱三色不变式,保证灰色到白色这个路径不会断开

删除屏障缺点

回收效率低,如果多次删除就会可能导致回收不成功,白白浪费一轮GC的回收时间。

三色标记法和混合写屏障

执行操作:

  • 首先GC将栈上的所有能够扫描到的对象全部扫描并标记为黑色。并且之后不再继续进行二次扫描,也不需要开启STW。
  • 在GC进行回收对象期间,程序并行在栈上的插入新对象的操作会使这个对象变为黑色。也就是说,只要在栈中新插入的对象全部变为黑色。
  • 只要是被删除的对象(指向该对象的指针不再指向就意味着删除)和被插入的对象(一个新指针指向该对象就代表被插入)都标记为灰色。

融合了插入屏障和删除屏障的优点。栈不启用屏障、堆启用屏障保护。并且几乎不用开启STW保护,在对栈上的对象赋为黑色的时候还是需要停止goroutine开启STW保护的。

总结

以上便是Golang的GC全部的标记-清除逻辑及场景演示全过程。

  • GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。

  • GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通

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

参考资料

垃圾回收器 | Go 程序员面试笔试宝典 (golang.design)

[Go三关-典藏版]Golang垃圾回收+混合写屏障GC全分析 - 知乎 (zhihu.com)

你可能感兴趣的:(golang,GC,面试题)