Go的垃圾回收机制

98 阅读6分钟

1. GC

垃圾回收是目前高级语言几基本必备的一种特性,特别是应用于业务性比较强的场景中的语言,比如Java、Go、python等,可以加大降低开发成本,将重点集中在业务流程上而不是内存管理上。

一些基本的概念:

  • STW(stop the world)

2. 标记清除算法

标记清除算法(Mark-Sweep)是Go语言v1.3版本之前采用的算法;

2.1 基本流程:

  • 进行STW
  • 标记阶段:开始标记,GC程序找出所有可达对象,并进行标记
    • 从根对象出发,依次遍历对象及其子对象,并标记对象的可达状态
  • 标记完成后,开始清除所有未标记的对象
    • 遍历所有对象,清除没有被标记的不可达对象
  • STW结束,恢复业务程序,直到下一次需要进行GC

2.2 标记清除算法的优缺点

优点:

  • 实现简单,可以解决循环引用问题

缺点:

  • 需要STW,标记前需要暂停业务线程

3. 三色标记法

在Go的v1.5版本,使用三色标记法进行优化,减少了STW的时间

image.png

3.1 算法介绍

将程序中的对象分为三类:

  • 白色:潜在的垃圾,未被垃圾收集器访问到的对象
    • GC初始阶段:所有的对象都被标记为白色
    • GC清除阶段:回收结束之后,所有的白色对象都不可达,内存会被释放
  • 灰色:活跃的对象,已经被垃圾回收器访问到,但是存在指向白色对象的外部指针,垃圾回收器需要继续扫描其子对象
  • 黑色:也是活跃的对象,已经被垃圾回收器访问到,也不存在指向白色对象的外部指针

算法步骤:

  • GC开始之前会STW,会有三个标记表来记录每个对象标记的颜色

  • 标记阶段:所有的对象默认标记为白色,如下图所示 image.png

  • 从根节点开始遍历,将根对象直接指向的对象标记为灰色,放到灰色标记表

image.png

  • 然后遍历灰色集合,将灰色对象标记为黑色,并由灰色标记表移动到黑色标记表
  • 重复上面遍历灰色集合、黑色集合两个步骤,直到灰色集合为空
  • 标记阶段结束,可以解除STW
  • 最后清除所有白色对象,完成垃圾回收

4. 屏障机制

在三色标记算法的执行过程中,如果没有STW,由于在标记过程中,用户程序更改了对象之间的引用关系,可能会引发一些内存回收不正确的问题

4.1 没有STW的三色标记

在垃圾回收的时候,致命的错误是:一个有被其他对象所引用的对象被清除

而三色标记如果在没有STW保护的情况下就可能出现这样的情况:

在标记过程中:

image.png

  • 如图所示,灰色对象F指向白色对象H
  • 由于在没有STW,所以任意对象的引用关系都可能发生读写;这时候假设还没有扫描到F的时候,已经标记为黑色的对象E增加指针q指向了对象H,同时对象F到对象H的p指针被删除

image.png

结果:对象E合法地引用对象H,但却被垃圾收集器“错误”回收

通过上述分析可以总结出出现问题的两个条件:

  • 条件1: 黑色对象引用白色对象
  • 条件2: 从灰色对象出发,到达白色对象、未经访问过的路径遭到破坏

有一个专业术语叫做:三色不变性,其含义就是在GC能够正确回收垃圾所需要满足一些性质

  • 强三色不变性:黑色对象不会指向白色对象,只会指向灰色对象或者白色对象(破坏条件1)

image.png

  • 弱三色不变性:黑色对象指向的白色对象,必须包含一条从灰色对象经由多个白色对象的可达路径(破坏条件2) image.png

只要能够满足三色不变性,GC就能正常工作

4.2 屏障技术

屏障技术就是用来保证三色不变性的技术技术,用于在没有STW的情况下,正确进行垃圾回收; 它像钩子方法,是在用户程序创建对象、读取对象、更新对象指针时执行的一段代码

Go语言在垃圾收集器的演进过程中,采用Dijkstra提出的插入写屏障和Yuasa提出的删除写屏障

为什么不采用读屏障?

因为堆上内存读取频率远高于写,所以采用读屏障的开销会更大

4.3 插入写屏障

v1.7 使用插入写屏障(狄杰斯特拉)

核心思路:在黑色对象增加对一个白色对象的引用的时候,将白色的对象改为灰色

image.png 一个对象在内存中可能存在的位置有两种:堆、栈

但是Go没有选择启用栈上的写屏障,因为栈需要的是响应速度快;在堆上开启了写屏障,栈上没有开启写屏障,就可能带来并发问题

所以需要对栈空间进行重新扫描,这时候需要进行STW直到栈空间的三色标记结束

4.4 删除写屏障

核心思路:在灰色对象引用白色对象时,用户将这个引用关系删除的时候会触发写屏障将被删除的白色对象标记为灰色

被删除对象在删除的时候被强制标记为灰色,满足弱三色不变性

优势:

  • 标记阶段结束后不需要重新扫描,也可以保证GC的正确性

劣势:

  • 但是回收不够彻底,一个对象的最后一个引用指针即使被删除了也会在本轮GC后存在,需要等到下一轮才能被清除

4.5 混合写屏障

v1.8 时引入,开始使用

流程:

  • 在GC开始的时候将栈上对象全部扫描并标记为黑色
  • GC期间:业务程序在栈上创建的新对象均为黑色
  • 堆上:被删除引用的被引用对象被比较标记为灰色(保护被引用对象的下游,删除写屏障)
  • 堆上:在黑色对象增加对一个白色对象的引用的时候,将白色的对象改为灰色(插入写屏障)

优势:

  • GC过程中几乎不需要STW,效率较高

劣势:

  • 较多的垃圾在本轮可能得不到回收,要在下一轮才能被回收