【golang】垃圾回收gc简介(1)

Page content

这一片简单的整理了 go的垃圾回收GC

C和C++以后出现的高级编程语言大部分都有垃圾回收(Garbage Collection)机制。
Go语言的垃圾回收GC是经历了3次比较大的改版。
依次是:

标记-清除(mark and sweep) => 三色标记法 => 三色标记法 + 混合屏障机制


1.标记-清除(mark and sweep)方法

go的1.3版本之前采用的是标记-清除(mark and sweep)方法。

1.1 基本原理

标记-清除(mark and sweep)方法就离不开STW(stop the world)。
需要暂停所有程序,清理垃圾后,再次启用程序。
如下图:

图片备用地址
go_gc_01

1.2 标记-清除(mark and sweep)方法的问题

  1. STW过程需要暂停程序,出现程序卡顿现象。(主要问题)
  2. 标记时需要扫描整改heap。
  3. 清除数据会产生heap碎片。

2.三色标记法

go的1.5版本开始使用了三色标记法。

2.1 基本原理

三色标记法主要是把对象标记成三种颜色的对象。白色,灰色,黑色。

  • 白色:未搜索的对象,在回收周期开始时所有对象都是白色,在回收周期结束时所有的白色都是垃圾对象
  • 灰色:正在搜索的对象,但是对象身上还有一个或多个引用没有扫描
  • 黑色:已搜索完的对象,所有的引用已经被扫描完
灰色:对象还在标记队列中等待
黑色:对象已被标记,gcmarkBits 对应位为 1 -- 该对象不会在本次 GC 中被回收
白色:对象未被标记,gcmarkBits 对应位为 0 -- 该对象将会在本次 GC 中被清理

三色标记法的五步进行过程:

  1. 将所有对象标记为白色
  2. 从根节点集合出发,将第一次遍历到的节点标记为灰色放入集合列表中
  3. 遍历灰色集合,将灰色节点遍历到的白色节点标记为灰色,并把灰色节点标记为黑色
  4. 循环这个过程
  5. 直到灰色节点集合为空,回收所有的白色节点

图片备用地址
go_gc_01

2.2 三色标记法的问题

条件1: 一个白色对象被黑色对象引用
条件2: 灰色对象与它之间的可达关系的白色对象去掉关系。

当同时满足这两个条件的时候,会清理有用的白色对象。
如下图:

图片备用地址
go_gc_06

2.3 解决方法

想预防如上问题,我们只需要破坏上述2钟情况的一种就可以。

2.3.1 强三色不变式

这方法是阻止条件1的产生。

强制不允许一个白色对象被黑色对象引用。

图片备用地址
go_gc_04

2.3.2 弱三色不变式

这方法是阻止条件2的产生。

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

图片备用地址
go_gc_05


3.混合屏障机制

为了防止产生三色表示法的问题,程序进行时添加了额外的屏障

3.1 插入屏障

在A对象应用B对象的时候,B对象会被标记为灰色。=> 强三色不变式
为了性能只能是在堆heap内存中使用,栈stack内存是不适用。

//伪代码
添加下有对象(当前下游对象slot, 新下游对象ptr){

    标记灰色(新下游对象ptr)

    当前下游对象slot = 新下游对象ptr
}

A.添加下有对象(B, nil) => A对象删除B对象的引用,B标记为灰色
A.添加下有对象(B, C) => A对象更换下游为C, B表示为灰色

下图中因对象8是被堆Heap内存的对象4引用,所以可以直接走插入屏障。
但是对象9是栈stack内存的对象1引用,所以不能走插入屏障。
只能是最后到STW暂停保护后,确保不会被清除。

图片备用地址
go_gc_07

插入屏障的不足

结束时,需要STW来重新扫描栈stack, 大约需要10~100ms的时间。

3.2 删除屏障

被删除的对象,如果自身为灰色或白色,都被标记为灰色。=> 弱三色不变式

//伪代码
添加下有对象(当前下游对象slot, 新下游对象ptr){

    if(当前下游对象slot == 灰色 || 当前下游对象slot == 白色){
        标记灰色(新下游对象ptr)
    }

    当前下游对象slot = 新下游对象ptr
}

A.添加下有对象(nil, B) => 给A新添加下游对象,把B标记为灰色
A.添加下有对象(C, B) => 下游对象C,更新为B,把B标记为灰色

图片备用地址
go_gc_08

删除屏障的不足

回收精度低:
一个对象即使被删除了最后一个指向它的指针也依旧可以存活这一轮,
到了下一轮GC中才能被清除掉。

4.Go语言的GC

go的1.8版本开始是三色标记法 + 混合写屏障hybrid write barrier。
需遵守如下规则:

  1. GC开始将栈上的对象全部扫描并标记为黑色(之后不在进行第二次扫描,无需STW)
  2. GC期间,任何在栈上的创建的对象,均为黑色
  3. 被删除的对象标记为灰色
  4. 被添加的对象标记为灰色

变形的弱三色不变式(结合了插入,删除写屏障的优点)

//伪代码
writePointer(slot, ptr):
    shade(*slot)
    if current stack is grey:
        shade(ptr)
    *slot = ptr

下面分析几个特殊场景,以便了解流程。

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

//前提:堆对象4 -> 对象7 = 对象7; //对象7 被 对象4引用
栈对象1 -> 对象7 = 堆对象7; //将堆对象7 挂在 栈对象1 下游
堆对象4 -> 对象7 = null; //对象4 删除引用 对象7

图片备用地址
go_gc_09

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

new 栈对象9;
对象9 -> 对象3 = 对象3;  //将栈对象3 挂在 栈对象9 下游
对象2 -> 对象3 = null;  //对象2 删除引用 对象3

图片备用地址
go_gc_10

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

堆对象10 -> 对象7 = 堆对象7;    //将堆对象7 挂在 堆对象10 下游
堆对象4 -> 对象7 = null;       //对象4 删除引用 对象7

图片备用地址
go_gc_11

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

栈对象1 -> 对象2 = null;    //对象1 删除引用 对象2
堆对象4 -> 对象2 = 栈对象2; //对象4 添加下游 栈对象2
堆对象4 -> 对象7 = null;    //对象4 删除引用 对象7

图片备用地址
go_gc_12


欢迎大家的意见和交流

email: li_mingxie@163.com