Golang三色标记、混合写屏障GC模式图文全分析

垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不须要的对象,让出存储器资源,无需程序员手动执行。html

         Golang中的垃圾回收主要应用三色标记法,GC过程和其余用户goroutine可并发运行,但须要必定时间的STW(stop the world),STW的过程当中,CPU不执行用户代码,所有用于垃圾回收,这个过程的影响很大,Golang进行了屡次的迭代优化来解决这个问题,本文将逐步推动Golang中GC的每次推动,来理解Gc的原理。程序员

〇、内容提纲算法

 

本文将系统的详细介绍Golang中GC的全分析过程,包括垃圾回收的方式递进。编程

    内容概要并发

  • G0 V1.3以前的标记-清除(mark and sweep)算法编程语言

  • Go V1.3以前的标记-清扫(mark and sweep)的缺点函数

  • Go V1.5的三色并发标记法优化

  • Go V1.5的三色标记为何须要STW3d

  • Go V1.5的三色标记为何须要屏障机制(“强-弱” 三色不变式、插入屏障、删除屏障 )指针

  • Go V1.8混合写屏障机制

  • Go V1.8混合写屏障机制的全场景分析

 

1、Go V1.3 以前的标记-清除(mark and sweep)算法

 

此算法主要有两个主要的步骤:

  • 标记(Mark phase)

  • 清除(Sweep phase)

第一步,暂停程序业务逻辑, 找出不可达的对象,而后作上标记。第二步,回收标记好的对象。

操做很是简单,可是有一点须要额外注意:mark and sweep算法在执行的时候,须要程序暂停!即 STW(stop the world)。也就是说,这段时间程序会卡在哪儿。

 

 

第二步, 开始标记,程序找出它全部可达的对象,并作上标记。以下图所示:

第三步, 标记完了以后,而后开始清除未标记的对象. 结果以下.


第四步, 中止暂停,让程序继续跑。而后循环重复这个过程,直到process程序生命周期结束。

 

 

2、标记-清除(mark and sweep)的缺点

 

  • STW,stop the world;让程序暂停,程序出现卡顿 (重要问题)。

  • 标记须要扫描整个heap

  • 清除数据会产生heap碎片

因此Go V1.3版本以前就是以上来实施的, 流程是

Go V1.3 作了简单的优化,将STW提早, 减小STW暂停的时间范围.以下所示

 

 

这里面最重要的问题就是:mark-and-sweep 算法会暂停整个程序 。

Go是如何面对并这个问题的呢?接下来G V1.5版本 就用三色并发标记法来优化这个问题.

 

3、Go V1.5的三色并发标记法

 

三色标记法 实际上就是经过三个阶段的标记来肯定清楚的对象都有哪些. 咱们来看一下具体的过程.

第一步 , 就是只要是新建立的对象,默认的颜色都是标记为“白色”.

 

这里面须要注意的是, 所谓“程序”, 则是一些对象的跟节点集合.

 

因此上图,能够转换以下的方式来表示.

第二步, 每次GC回收开始, 而后从根节点开始遍历全部对象,把遍历到的对象从白色集合放入“灰色”集合。

 

第三步, 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,以后将此灰色对象放入黑色集合

 

第四步, 重复第三步, 直到灰色中无任何对象.

 

 

第五步,回收全部的白色标记表的对象. 也就是回收垃圾.

以上即是三色并发标记法, 不难看出,咱们上面已经清楚的体现三色的特性, 那么又是如何实现并行的呢?

Go是如何解决标记-清除(mark and sweep)算法中的卡顿(stw,stop the world)问题的呢

4、没有STW的三色标记法

 

        咱们仍是基于上述的三色并发标记法来讲, 他是必定要依赖STW的. 由于若是不暂停程序, 程序的逻辑改变对象引用关系, 这种动做若是在标记阶段作了修改,会影响标记结果的正确性。咱们举一个场景.

若是三色标记法, 标记过程不使用STW将会发生什么事情?

 

 


 


 


 


能够看出,有两个问题, 在三色标记法中,是不但愿被发生的

  • 条件1: 一个白色对象被黑色对象引用(白色被挂在黑色下)

  • 条件2: 灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)

当以上两个条件同时知足时, 就会出现对象丢失现象!

 

固然, 若是上述中的白色对象3, 若是他还有不少下游对象的话, 也会一并都清理掉.

为了防止这种现象的发生,最简单的方式就是STW,直接禁止掉其余用户程序对对象引用关系的干扰,可是STW的过程有明显的资源浪费,对全部的用户程序都有很大影响,如何能在保证对象不丢失的状况下合理的尽量的提升GC效率,减小STW时间呢?

答案就是, 那么咱们只要使用一个机制,来破坏上面的两个条件就能够了.

5、屏障机制

 

咱们让GC回收器,知足下面两种状况之一时,可保对象不丢失. 因此引出两种方式.

 

“强-弱” 三色不变式

01

 

  • 强三色不变式

不存在黑色对象引用到白色对象的指针。

 

  • 弱三色不变式

全部被黑色对象引用的白色对象都处于灰色保护状态.

 

 

为了遵循上述的两个方式,Golang团队初步获得了以下具体的两种屏障方式“插入屏障”, “删除屏障”.

 

插入屏障

02

具体操做: 在A对象引用B对象的时候,B对象被标记为灰色。(将B挂在A下游,B必须被标记为灰色)

知足: 强三色不变式. (不存在黑色对象引用白色对象的状况了, 由于白色会强制变成灰色)

伪码以下:

 

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

//2

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

场景:

 

A.添加下游对象(nil, B) //A 以前没有下游, 新添加一个下游对象B, B被标记为灰色
A.添加下游对象(C, B) //A 将下游对象C 更换为B, B被标记为灰色

这段伪码逻辑就是写屏障,. 咱们知道,黑色对象的内存槽有两种位置, . 栈空间的特色是容量小,可是要求相应速度快,由于函数调用弹出频繁使用, 因此“插入屏障”机制,在栈空间的对象操做中不使用. 而仅仅使用在堆空间对象的操做中.

接下来,咱们用几张图,来模拟整个一个详细的过程, 但愿您可以更可观的看清晰总体流程。


 


 


 


 


 


 


可是若是栈不添加,当所有三色标记扫描以后,栈上有可能依然存在白色对象被引用的状况(如上图的对象9). 因此要对栈从新进行三色标记扫描, 但此次为了对象不丢失, 要对本次标记扫描启动STW暂停. 直到栈空间的三色标记结束.


 


 


 


最后将栈和堆空间 扫描剩余的所有 白色节点清除. 此次STW大约的时间在10~100ms间.


 


 

 

删除屏障

03

具体操做: 被删除的对象,若是自身为灰色或者白色,那么被标记为灰色。

知足: 弱三色不变式. (保护灰色对象到白色对象的路径不会断)

伪代码:

 

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

//2

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

场景:

 

A.添加下游对象(B, nil) //A对象,删除B对象的引用。B被A删除,被标记为灰(若是B以前为白)
A.添加下游对象(B, C) //A对象,更换下游B变成C。B被A删除,被标记为灰(若是B以前为白)

接下来,咱们用几张图,来模拟整个一个详细的过程, 但愿您可以更可观的看清晰总体流程。


 


 


 


 


 


 


 


这种方式的回收精度低,一个对象即便被删除了最后一个指向它的指针也依旧能够活过这一轮,在下一轮GC中被清理掉。

6、Go V1.8的混合写屏障(hybrid write barrier)机制

 

插入写屏障和删除写屏障的短板:

  • 插入写屏障:结束时须要STW来从新扫描栈,标记栈上引用的白色对象的

    存活;

  • 删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个

    过程会保护开始时刻的全部存活对象。

Go V1.8版本引入了混合写屏障机制(hybrid write barrier),避免了对栈

re-scan的过程,极大的减小了STW的时间。结合了二者的优势。


 

 

混合写屏障规则

01

具体操做:

一、GC开始将栈上的对象所有扫描并标记为黑色(以后再也不进行第二次重复扫描,无需STW),

二、GC期间,任何在栈上建立的新对象,均为黑色。

三、被删除的对象标记为灰色。

四、被添加的对象标记为灰色。

知足: 变形的弱三色不变式.

伪代码:

 

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

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

//3

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

这里咱们注意, 屏障技术是不在栈上应用的,由于要保证栈的运行效率。

 

混合写屏障的具体场景分析

02

        接下来,咱们用几张图,来模拟整个一个详细的过程, 但愿您可以更可观的看清晰总体流程。

注意混合写屏障是Gc的一种屏障机制,因此只是当程序执行GC的时候,才会触发这种机制。

GC开始:扫描栈区,将可达对象所有标记为黑

 


 


 

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

 

 

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

 


 


 

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

 

 

new 栈对象9;

对象9->对象3 = 对象3;      //将栈对象3 挂在 栈对象9 下游

对象2->对象3 = null;      //对象2 删除引用 对象3


 


 


 


 

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

 

 

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


 


 


 


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

 

 

栈对象1->对象2 = null;      //对象1 删除引用 对象2

堆对象4->对象2 = 栈对象2;    //对象4 添加 下游 栈对象2

堆对象4->对象7 = null; //对象4 删除引用 对象7null;//对象4 删除引用 对象7

 


 


 


        Golang中的混合写屏障知足弱三色不变式,结合了删除写屏障和插入写屏障的优势,只须要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不须要STW,而标记结束后,由于栈在扫描后始终是黑色的,也无需再进行re-scan操做了,减小了STW的时间。

 

 


 

7、总结

        以上即是Golang的GC所有的标记-清除逻辑及场景演示全过程。

GoV1.3- 普通标记清除法,总体过程须要启动STW,效率极低。

GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,所有扫描以后,须要从新扫描一次栈(须要STW),效率普通

GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不须要STW,效率较高。

 

 

参考文献:

https://www.cnblogs.com/wangyiyang/p/12191591.html https://www.jianshu.com/p/eb6b3aff9ca5 https://zhuanlan.zhihu.com/p/74853110