golang中的GC原理

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

GOLANG的垃圾回收机制
现在go1.14所用的是三色标志法和GC混合写屏障,GC过程和其他用户goroutine可并发运行,但需要一定时间的STW(stop the world),STW的过程中,CPU不执行用户代码,全部用于垃圾回收

GC版本变化
Go V1.3 标记清除(mark and sweep)
Go V1.5 三色标记法
Go V1.8 加入混合写屏障

Go V1.3的标记清除
标记清除的流程
触发GC,暂停程序业务逻辑(STW),找出可达对象和不可达对象

在这里插入图片描述

如图所示,箭头表示引用关系,1,2,3,4,7这五个对象就是可达对象,5和6这两个对象就是不可达对象
开始标记,找到它所有的可达对象,并做上标记,如上图中的红色对象部分。
完成标记,开始清除未标记的对象
完成清除,停止暂停,然后循环重复以上四步,直到该程序进程生命周期结束
整个流程的图示:

在这里插入图片描述
问题:触发GC的条件是什么

标记清除的缺点
1. 存在STW。因为在GC阶段需要暂停整个程序,程序出现卡顿,影响性能(最严重的问题 )
2. 标记过程需要扫描整个heap和stack(堆栈信息),无疑是加长了整个STW的持续时间,还是影响性能的问题
3. 清除数据后会产生heap碎片,也就是产生一些不连续的碎片内存空间,对后续的申请内存增加困难
标记清除的优化办法
标记清除的GC策略最主要问题在于STW时间过长,所以优化方向就是减少STW时间,如下图:

在这里插入图片描述

即调整GC流程的执行顺序,将Sweep操作挪出STW,使GC流程可以和程序业务逻辑并发执行,以此达到缩短STW时间的目的

但是效果有限,前面说到的找到可达对象可标记的过程涉及整个内存空间,占了时间大头。那么优化GC要从标记出发,想方设法地在标记阶段减少STW,但传统普通的标记清除难以保证在没有STW的情况下不丢失对象(后面有提及),所以之后摈弃了传统的标记方法,提出了三色标记法

Go V1.5的三色标记法
三色分别指的是:

白色标记表
灰色标记表
黑色标记表
三色标记的过程
1.开始标记,只要是创建的对象,最开始颜色都是标记为"白色",以上图为例,对象1-7都是白色
2.从程序(对象根节点)出发,即从上图的程序出发,开始遍历所有对象,把可达的对象从白色集合放入到灰色集合(不递归),当前对象1,对象4为灰,其余都为白
3.遍历灰色集合,将灰色对象引用到的对象从白色集合放入灰色集合,之后将原灰色对象放入黑色集合,当前1,4为黑,2,7为灰,3,5,6为白
4. 重复第三步,直到灰色表中无任何对象
5. 回收所有白色标记的对象,也就是回收垃圾

三色标记法的探讨
三色标记法的出现肯定是为了优化GC的效率,最重要的一点就是缩短STW的时间

问题: 三色标记法也是扫描整个堆栈空间,和之前的标记清除有什么优势呢?

答: 就是把标记的过程移出STW的过程

但是简单移除会带来很大风险,在同时满足以下2个条件后会误删除对象(这一现象称为悬挂指针):

1. 一个白色对象当前被黑色对象引用(即白色被挂在黑色下)
2. 灰色对象与该白色对象之间的引用关系被破坏(即灰色同时丢失了该白色)
要想在标记阶段没有STW的存在,就要阻止以上两个条件同时发生的情况

所以在对象引用条件上提出了强三色不变式弱三色不变式两种规则,分别从上面两个条件出发,防止引用对象被误删除的情况发生

强三色不变式

在这里插入图片描述
强制性的不允许黑色对象引用白色对象,破坏条件1

弱三色不变式
黑色对象可以引用白色,但同时该白色对象必须存在其他灰色对象对它的引用,或者是在该白色对象引用链路的上游存在灰色对象,破坏条件2

在这里插入图片描述
在三色标记中只要满足强/弱三色不变式的一种,即可保证引用对象不丢失

总结:
三色标记法相比传统标记法,对GC效率提升的关键就是将标记的过程挪出STW。但三色标记在如上2个条件同时满足时会丢失引用对象,后来提出强/弱三色不变式规则来防止两个条件同时成立,下面的问题就是如何实现强/弱三色不变式规则,即屏障机制

屏障机制
实现强/弱三色不变式,方法可以分为下面两种

插入写屏障(对象被引用时出发的机制)
删除写屏障(对象被删除时,也即解除引用关系时触发的机制)


插入写屏障

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

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

伪码:

在这里插入图片描述

插入屏障只限制堆上的对象,不限制栈上的对象

我们知道对象存储是在堆上或者是栈上,因为每次插入都要做判断的话会影响性能。如果运行时需要在几百个 Goroutine 的栈上都开启写屏障,会带来巨大的额外开销,又加上栈空间比较小,但是要求相应速度快,因为函数调用弹出频繁使用,所以在栈上是没有插入屏障的,只在堆上有

插入写屏障的不足
因为插入屏障不限制栈上对象的原因,所以在三色标记法标记完整个heap之后,要启动STW,把栈上黑色对象全部置为白色,重新遍历扫描一次栈空间。因为栈空间比较小,所以耗时很短,大约需要10-100ms

删除写屏障

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

删除屏障的不足:

回收精度低,一个对象即使被删除了最后一个指向它的指针,它也依旧可以活过这一轮GC,在下一轮GC中才能被清除掉。不过该问题只是影响当前可用内存大小,可以忽略

总结
1.5版本在标记过程中使用三色标记法。回收过程主要有四个阶段,其中,标记和清理都并发执行的,但标记阶段的前后需要STW一定时间来做GC的准备工作和栈的re-scan

Go V1.8的三色标记法+混合写屏障机制
前面介绍了三色标记法的过程。同时知道要想提高GC的效率,需要把标记阶段没有STW,但在两个条件同时满足时,会丢失对象。为了避免两个条件同时发生,提出了强/弱三色不变式,对应的实现分别是插入写屏障删除写屏障。但两个都有自己的不足,混合写屏障为了保证性能,不能在栈上对象执行,仍然需要STW来保障标记。删除写屏障回收精度低(同时在回收前仍需要短暂STW来记录当前快照)

针对以上不足,Go在V1.8结合了插入写屏障和删除写屏障的优点,提出混合写屏障(hybrid write barrier)机制

具体操作:

GC开始,所有对象默认都是白色。优先将栈上的可达对象全部扫描并标记为黑色(之后不再向插入写屏障时进行第二次重复扫描,无需STW)
GC期间,任何在栈上创建的新对象和引用白色对象,均为黑色
堆上被删除的白色对象(解除引用关系)标记为灰色
堆上被添加的白色对象标记为灰色
注意混合写屏障是GC的一种屏障机制,所以只是当程序执行GC的时候,才会触发这种机制。

满足:
变形的弱三色不变式。结合了插入,删除写屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间。

总结
在V1.3采用的是传统普通的标记清除法,全程需要STW,效率极低。此时提升GC效率的重要方向就是在标记阶段缩短STW,然而传统的标记清除不能保证没有STW的情况下不丢失数据。V1.5版本提出三色标记法,结合插入写屏障或者是删除写屏障,能大大缩短STW的同时,保证不丢失对象。V1.8 结合了插入/删除写屏障的优点,提出了混合写屏障,在保证不丢失数据的同时,几乎没有STW的存在

GC触发的条件:

定量触发:Go 语言运行时的默认配置会在堆内存达到上一次垃圾收集的 2 倍时,触发新一轮的垃圾收集,这个行为可以通过环境变量 GOGC 调整,在默认情况下它的值为 100,即增长 100% 的堆内存才会触发 GC。
定时触发:如果一定时间内没有触发,就会触发新的循环,该出发条件由 runtime.forcegcperiod 变量控制,默认为 2 分钟;
手动触发: 用户程序会通过 runtime.GC 函数在程序运行期间主动通知运行时执行,该方法在调用时会阻塞调用方直到当前垃圾收集循环完成,在垃圾收集期间也可能会通过 STW 暂停整个程序:
空间不足时触发: 当前线程的内存管理单元中不存在空闲空间时,创建32KB以下的对象可能触发垃圾收集,创建32KB以上的对象时,一定会尝试触发
 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值