Go的GC回收机制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go的GC回收机制相关的知识,希望对你有一定的参考价值。

一、GoV1.3的标记清除法

原理:

  • 第一步,暂停程序业务逻辑,找出不可达的对象和可达对象
  • 第二步,开始标记,程序找出它所有可达的对象,并做上标记
  • 第三部,标记完了之后,然后开始清除未标记的对象
  • 第四步,停止暂停,让程序继续跑,然后循环重复这个过程,知道process程序生命周期结束

缺点:

  • STW:stop the world 让程序暂停,程序出现卡顿
  • 标记需要扫描整个stop
  • 清除数据会产生heap碎片
  • 将第四步和第三部换位置,缩短STW的范围

STW的过程:




二、GoV1.5三色标记法

  • 第一步:就是只要是新创建的对象,默认颜色都是标记白色
  • 第二步:每次GC回收开始,然后从根节点开始遍历所有对象,把遍历的对象从白色放入灰色集合
  • 第三步:遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合
  • 第四步:重复第三部,直到灰色中无任何对象
  • 第五步:回收所有的白色标记的对象,也就是回收垃圾

三色标记法最不希望发生的事:

两个条件同时满足,就会出现对象丢失现象

  • 条件1:一个白色对象被黑色对象引用(白色被挂在黑色下)
  • 条件2:灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了白色)

避免这种现象最简单的方式就是STW,但是STW的过程有明细的资源浪费,对所有的用户程序都有很大影响
如何能在保证对象不丢失的情况下尽可能的提高GC效率,减少STW时间呢?


解决方法:

强三色不变式

强制性的不允许黑色对象引用白色对象,破坏了条件1

弱三色不变式

黑色对象可以引用白色对象,白色对象存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象
如果满足三色标记强弱不变式之一,即可保证不丢失对象

屏障机制:


插入屏障

插入屏障不在栈上使用,为了保证栈的速度

结束时需要STW来重新扫描栈,大约需要10-100ms

删除屏障

具体操作:如果被删除的对象,如果自身为灰色或者白色,那么会被标记为灰色
满足:弱三色不变式(保护灰色对象到白色对象的路径不会断)

删除写屏障的不足

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

小结

插入写屏障的不足:结束时需要STW来重新扫描栈,大约需要10~100ms
删除写屏障的不足:回收精度低,一个对象即使被删除了最后一个指向它的指针,也依旧可以活过这一轮,在下一轮GC中被清理掉

三、GoV1.8三色标记法+混合写屏障机制

具体操作:

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

满足:变形的弱三色不变式(结合了插入、删除写屏障两者的优点)

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


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



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



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



Go的内存回收机制(三色屏障)

参考原文

垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不需要的内存对象,让出存储器资源。GC过程中无需程序员手动执行。GC机制在现代很多编程语言都支持,GC能力的性能与优劣也是不同语言之间对比度指标之一。

Go V1.13之前的标记-清除(mark and sweep)算法

一、暂停程序业务逻辑,分类出可达和不可达对象,然后做上标记

二、清除所有未标记的对象

整个操作非常简单,但是在mark and sweep算法执行时,程序必须暂停.即STW(stop the world).

STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程很耗费时间

三、停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束


但是缺点也很明显

  • STW, 让程序暂停, 耗时卡顿(重要问题)
  • 标记需要扫描整个heap
  • 清除数据会产生heap碎片

在Go V1.3做了一个简单优化,在开启STW的过程中只完成标记步骤,在停止STW后才开始sweep清除

不过仍然无法避免上面的缺点.

Go V1.5的三色标记法

该算法GC过程中可以和用户goroutine并发运行,但需要一小段时间的STW

算法过程中三色分别是白表,灰表,黑表

Ⅰ.程序创建初,把所有对象标记未白色放入白表中

Ⅱ.GC回收开始,从程序根节点开始遍历所有相邻对象,把遍历到的对象从白色集合中放入灰色集合

Ⅲ.遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,然后把自己放入灰色集合

Ⅳ.不断重复第三步,直到没有灰色节点,此时白表中的对象就是垃圾,可以被回收.

这么看下来好像只是一个简单的bfs,设置的颜色似乎也很多余.

如果给这个GC过程加上STW,会很浪费时间,没有优化多少.

如果不加STW,那么在回收过程中,对象间相互改变引用关系,会使垃圾回收错误.比如下面

此时[对象2]去掉对[对象3]的引用,同时[对象4]加上对[对象3]的引用

那么对象3仍然不会是垃圾,然而此时灰色节点已经到不了对象3了,对象3最后会成为垃圾被错误回收

所以在三色标记法中,会出现错误的原因有两个

  • 条件1: 一个白色对象被黑色对象引用
  • 条件2: 一个白色对象和灰色对象的可达关系遭到破坏

但是我们又不想使用STW, 于是引入了一种屏障机制来破坏上述的两个条件,保证对象不丢失.

三色标记的屏障机制

强三色不变式

实质上是强制的不允许黑色对象引用白色对象, 这样就不会出现白色对象被误删的情况

插入屏障

在A对象引用B对象时,B对象被标记未灰色.

此时满足强三色不变式(不存在黑色对象引用白色对象的情况,因为白色会强制变为灰色)

添加下游对象(当前下游对象slot, 新下游对象ptr)    
  //1
  标记灰色(新下游对象ptr)   
  //2
  当前下游对象slot = 新下游对象ptr  				  

插入屏障是一个很耗费性能的行为,而栈需要更高的性能要求,因此,插入屏障技术只运用在堆内存空间里,不会运用到栈里。

等全部三色标记扫描后,栈上可能仍存在白色对象被引用的情况,于是使用STW暂停直到栈空间的三色标记结束(这次的STW大概时间在10~100ms之间)


弱三色不变式

允许黑色对象引用白色对象时, 但需要满足条件

只有当这个白色对象存在其他灰色对象对它的引用,或它的可达链路上存在灰色对象时

删除屏障

被删除的对象,如果自身为灰色或者白色,那么就会被标记为灰色。

当然,这种方式的回收精度比较低,可能那个被删除的对象本身就是个垃圾,但是我们仍然把它在这一轮变成灰色, 无法回收。

添加下游对象(当前下游对象slot, 新下游对象ptr) 
  if (当前下游对象slot是灰色 || 当前下游对象slot是白色) 
  		标记灰色(当前下游对象slot)     //slot为被删除对象, 标记为灰色
  
  当前下游对象slot = 新下游对象ptr

混合写屏障规则

①.GC开始把栈上所有对象全部扫描并标记为黑色(之后无需二次重复扫描,无需STW)

②.GC期间, 任何在栈上创建的新对象均为黑色

③.被删除的对象标记为灰色

④.被添加的对象标记为灰色

添加下游对象(当前下游对象slot, 新下游对象ptr) 
	标记灰色(当前下游对象slot)    //只要当前下游对象被移走,就标记灰色
  	标记灰色(新下游对象ptr)
  	当前下游对象slot = 新下游对象ptr

这里注意,屏障技术是不在栈上应用的,因为要保证栈的运行效率

以上是关于Go的GC回收机制的主要内容,如果未能解决你的问题,请参考以下文章

Go的内存回收机制(三色屏障)

Go的内存回收机制(三色屏障)

搞懂Go垃圾回收

回收机制GC

Android 内存回收机制

go的垃圾回收机制