golang gc 详解

golang 的 GC 经历过很多次变革, 主要大更新的版本是

  • 标记清除: v1.3
  • 三色标记: v1.5
  • 混合写屏障+三色标记: v1.8

golang 的 GC 不断的提升并发性能并且**减少STW (Stop The World)**的时间

标记清除

步骤

  • stw 暂停程序
  • mark: 标记可达对象(分类出可对象和不可达对象)
  • sweep: 清除不可达对象
  • stw 结束

怎么 mark 标记可达对对象?

从程序的根节点往下面访问, 能遍历到的就是可达对象, 遍历不到的就是不可达的对象

如下图所示: ACE 都是可达对象, BD 是不可达对象

标记清除算法的优化

标记清除算法整个 GC 都是在 STW, STW 的时间过长了

为了减少 STW 的时间, golang 将 STW 的范围缩小

只是在 mark 的是否 STW, sweep 和程序并发执行

但是整个的 STW 还是挺长的, 特别是当需要 mark 标记的对象越多, 需要 STW 的时间越多

于是 go 1.5 就用三色标记法来动态的滚动式的做 gc 将一次长的 stw 分散到多次短暂 stw 中去

三色标记

步骤

  • 遍历根对象的第一层可达对象标记为灰色, 不可达默认白色
  • 将灰色对象的下一层可达对象标记为灰色, 自身标记为黑色
  • 多次重复步骤2, 直到灰色对象为0, 只剩下白色对象和黑色对象
  • sweep 白色对象

示例

  • 遍历根对象的第一层可达对象标记为灰色, 不可达默认白色
  • 将灰色对象 A 的下一层可达对象标记为灰色, 自身标记为黑色
  • 继续遍历灰色对象的下层对象,重复步骤2
  • 继续遍历灰色对象的下层对象,重复步骤2
  • 灰色对象为0, 清理白色对象

三色标记对象丢失

如果 gc 期间不 stw 的话有可能对象丢失

一个黑色对象在 gc 期间链接了白色对象, 白色对象又没有任何的灰色对象可达就会导致对象的丢失

举个例子

(1) 上图刚刚标记完, 准备清除(此时程序并发执行 E 对象链接了B对象)

此时按道理 BD 式可达的, 但是此时 BD 会被当成垃圾清理掉,造成对象丢失

三色标记对象丢失解决

对象丢失是在GC里面绝对不被允许的, 可以暂时存在垃圾但是不能丢失

所以还是要 STW

为了解决这个 STW 的问题, 引入了强三色不变式和弱三色不变式, 只要满足强三色不变式和弱三色不变式的任意一种就能解决 STW 的问题

  • 强三色不变式: 一个黑色对象在 gc 期间链接了白色对象
  • 弱三色不变式: 黑色对象链接的白色对象又没有任何的灰色对象可达

三色标记+混合写屏障就能解决这个问题

三色标记 + 混合写屏障

步骤

  • STW 扫描栈, 将可达对象标记为黑色(混合写屏障也是要STW的, 网上很多文章都没说, scan stack 的时候要 STW,只是混合写屏障去掉了 rescan stack 这个步骤,时间在 1ms 左右)
  • gc 期间 stack 创建的对象都是灰色
  • gc 期间在堆添加混合写屏障(链接/删除链接 元素都标记为灰色,屏障会略微减少程序性能, 因为会加一层)

示例: 解决对象丢失问题

(1) 上图刚刚标记完, 准备清除(此时程序并发执行 E 对象链接了B对象)

链接对象会经过混合写屏障, 新插入的元素会标记为灰色

(2) 接下来按照三色标记继续遍历即可

go GC 和 Java GC 有什么区别

golang 的 gc 的不断演化通过将 STW 分散化大大减少 STW 的时间

最新版本的 golang 的 gc 基本都是在 1ms 以内, 但是这样故意的设计是牺牲吞吐量换来的

gc 的频率会比其他的垃圾回收更高比如 jvm 和 .net高

go 目前的混合写屏障 + 三色标记标记在 gc 期间的屏障也会给性能带来一定的损耗

但是总体的损耗目前的机器配置完全能 cover,

  • go 的开发效率很高
  • go 的主要应用领域不是 cpu 敏感型,在高 IO 的领域有很好的性能
  • go 要避免跟 jvm 和 .net 这样给高吞吐的 gc 正面竞争, 才能发挥出 go 超短 stw 的优势