Go语言的垃圾回收机制(简易版)

316 阅读5分钟

本文结构速览

image.png

Go语言 V1.3之前的标记清除机制

和其他编程语言一样,go语言也有自己的垃圾回收机制。在GO V1.3之前的版本当中,采用的是Mark和Sweep的机制进行垃圾回收。具体来说,在一个程序运行的时候,会创建许多对象,此时在堆空间当中,是可以直接访问到这些对象的。当需要进行垃圾回收的时候,程序首先暂停程序运行所有的服务,然后从根对象开始扫描,直到获得所有可以访问到的对象,剩下那些没有被扫描到的对象,就是我们所需要回收的垃圾。整体过程如下图所示:

image.png

  1. 首先程序触发STW(stop the word),暂停当前所有服务。
  2. 程序从根对象开始扫描,按照引用顺序扫描所有可达对象,如上图。
  3. 程序标记所有可达对象,并将不可达到的对象释放掉。
  4. 恢复程序服务。

在 Go 语言的垃圾回收机制中,根对象指的是那些被程序直接引用的对象,例如全局变量、当前运行函数的参数和局部变量、还有程序栈中的对象等。这些对象被认为是活动对象,也就是说它们可以被访问到,因此不能被垃圾回收器回收。在 Go 中,垃圾回收器会从根对象开始遍历整个对象图,标记所有可达的对象,并进行垃圾回收。如果一个对象不可达,即无法通过根对象访问到,那么它就会被回收。通过这种方式,Go 语言的垃圾回收机制能够自动管理内存,避免了像 C 和 C++ 这样需要手动管理内存的问题。

标记清除法非常简单,但是对程序造成的影响比较大,毕竟暂停程序服务的这段时间,程序是什么也做不了的。

Go语言 V1.5的三色标记法

为了消除STW对程序造成的影响,go语言在1.5版本中引入了三色标记法。具体来说,其基本流程如下图所示:

image.png 图1.1 三色标记法启动时

image.png 图1.2 第一轮扫描

image.png 图1.3 第二轮扫描

image.png 图1.4 n轮扫描之后

  1. 创建对象时所有对象默认为白色。
  2. 遍历根对象(只遍历一次,遍历邻接对象),得到灰色节点。(初始时所有节点都标记为白色)
  3. 遍历灰色标记表,得到其可达对象,将其标记为白色,然后将此次灰色表中被遍历之后的节点标记为黑色。
  4. 重复步骤3,直到灰色标记表中无对象为止。
  5. 回收白色标记表中的对象。

三色标记法的问题

  1. 在不使用STW时,如果程序中某个对象在标记为白色的时候,已标记为黑色的对象指向了没有被指向的对象,那么此时这个白色的对象本不应该被回收,却在垃圾回收的时候被回收。

image.png

造成这个问题的原因

  1. 一个白色对象被黑色对象引用。(白色被挂在黑色下)
  2. 灰色对象与他之间的可达关系的白色对象遭到破坏。(灰色同时丢了该白色对象)
  3. 1,2同时满足时,就会出现对象丢失现象。

强三色不变式和弱三色不变式

强三色不变式

强制性不允许黑色对象引用白色对象。

弱三色不变式

黑色对象可以引用白色对象,白色对象存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。

屏障机制

插入写屏障

对象被引用时触发的机制。具体操作:在A对象引用B对象的时候,B被标记为灰色。(将B挂在A的下游,B必须被标记为灰色)。这种机制满足了强三色不变式

// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
    // 1
    标记灰色(新下游对象ptr)
    // 2
    当前下游对象slot = 新下游对象ptr
}
  • 场景1.A添加下游对象(nil,B)// A之前没有下游,新添加一个下游对象B,B标记为灰色
  • 场景2.A添加下游对象(C,B) // A将下游对象C换成B,B被标记为灰色

不足

无法处理栈空间内的黑色对象已引用白色对象,需要在结束时使用STW重新扫描栈区。

删除写屏障

对象被删除前触发的机制。具体操作:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。这种机制满足了弱三色不变式

// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
    // 1
    if(当前下游对象slot是灰色 || 当前下游对象slot是白色){
        标记灰色(当前下游对象) // slot为被删除的对象,标记为灰色
    }
    
    // 2
    当前下游对象slot = 新下游对象ptr
}
  • 场景1.A添加下游对象(B,nil)// A之前没有下游,新添加一个下游对象B,B标记为灰色
  • 场景2.A添加下游对象(B,C) // A将下游对象C换成B,B被标记为灰色

不足

回收精度低

go语言混合写屏障机制

具体操作:

  1. GC开始时将栈上的对象全部扫描并标记为黑色。
  2. GC期间,任何在栈上创建的新的对象,均为黑色。
  3. 被删除的对象标记为灰色。
  4. 被添加的对象标记为灰色。 满足:变形的弱三色不变式(结合了插入、删除写屏障两者的优点)
// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
    // 1
    标记灰色(当前下游对象slot)// 只要当前下游对象被移走,就标记为灰色
    // 2
    标记灰色(新下游对象ptr)
    // 3
    当前下游对象slot = 新下游对象ptr
}