关于Golang GC的一些误解--真的比Java算法更领先吗?

导读:Golang的GC算法经过12个版本的发展,现在已经非常成熟了。本文对Golang的GC进行深入调研,并且通过具体实例揭示了Golang GC的方方面面。


首先强调下本文的起因是在高可用架构后花园群的一次聊天,大家在争论Golang的GC到底是类似Java的ZGC还是类似Java的CMS GC。我个人的看法是Golang的GC是类似于Java的CMS GC,官方的mgc的注释这么说的:

 
 

// The GC runs concurrently with mutator threads, is type accurate (aka precise), allows multiple// GC thread to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is// non-generational and non-compacting. Allocation is done using size segregated per P allocation// areas to minimize fragmentation while eliminating locks in the common case.
其中mutator是指我们的应用程序,因为可能会改变内存的状态,所以命名为mutator。这段话翻译过来,大概的意思就是说Go的GC使用的是一种非分代的没有整理过程的Concurrent Mark and Sweep算法(CMS算法),我个人再补充下,标记过程(即Mark过程)是使用三色标记法。讨论的过程中出现两个错误观点,一个是CMS算法一定是分代的,另一个是使用了三色标记法就是类似于ZGC的做法(我个人不知道为啥有这个观点,当时也忘记问清楚了,可能是把三色标记法和ZGC里的指针染色搞混了)。至于CMS是否一定要分代,我给一篇介绍,再借用R大的一句话给问题先做个结论,“只要不移动对象做并发GC,最终就会得到某种形式的CMS。”


标记-清理算法


标记-清理算法是一种追踪式的垃圾回收算法,并不会在对象死亡后立即将其清理掉,而是在一定条件下触发,统一校验系统中的存活对象,进行回收工作。

标记-清理分为两个部分,标记和清理,标记过程会遍历所有对象,查找出死亡对象。通过GC ROOT到对象的可达性就可以确认对象的存活,也就是说,如果存在一条从GC ROOT出发的引用最终可指向某个对象,就认为这个对象是存活的。这样,未能证明存活的对象就可以标记为死亡了。标记结束后,再次进行遍历,清理掉确认死亡的对象。

标记清理都是并发执行的标记-清理算法就是CMS。三色标记法是一种标记对象使用的算法。


Go GC的改进历史

  • 1.3以前的版本使用标记-清理的方式,整个过程都需要STW。

  • 1.3版本分离了标记和清理的操作,标记过程STW,清理过程并发执行。

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

  • 1.8版本在引入混合屏障rescan来降低mark termination的时间


GC 流程 1.5


640?wx_fmt=png

  1. Sweep Termination: 收集根对象,清理上一轮未清扫完的span,启用写屏障和辅助GC,辅助GC将一定量的标记和清扫工作交给用户goroutine来执行,写屏障在后面会详细说明。

  2. Mark: 扫描所有根对象和通过根对象可达的对象,并标记它们

  3. Mark Termination: 完成标记工作,重新扫描部分根对象(要求STW),关闭写屏障和辅助GC

  4. Sweep: 按标记结果清理对象


GC 1.8


1.8引入混合屏障,最小化第一次STW,混合屏障是指:
写入屏障,在写入指针f时将C对象标记为灰色。Go1.5版本使用的Dijkstra写屏障就是这个原理,伪代码如下:

writePointer(slot, ptr):    shade(ptr)    *slot = ptr

删除屏障,使用的Yuasa屏障伪代码如下:

writePointer(slot, ptr):    if (isGery(slot) || isWhite(slot))        shade(*slot)    *slot = ptr


1.8中引入的混合屏障,写入屏障和删除屏障各有优缺点,Dijkstra写入写屏障在标记开始时无需STW,可直接开始,并发进行,但结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;Yuasa删除屏障则需要在GC开始时STW扫描堆栈来记录初始快照,这个过程会记录开始时刻的所有存活对象,但结束时无需STW。Go1.8版本引入的混合写屏障结合了Yuasa的删除写屏障和Dijkstra的写入写屏障,结合了两者的优点,伪代码如下:


writePointer(slot, ptr):    shade(*slot)    if current stack is grey:        shade(ptr)    *slot = ptr


因此,个人的理解是在Mark init阶段开始的时候激活混合写屏障这时候STW,在rescan阶段应该也只需要在去掉混合写屏障的时候STW。从算法上来看,是接近Java CMS算法,而非ZGC,当然Go GC的比Java CMS GC有很多实现上的优化。


为了了解其细节,查到William有篇文章讲了不少GC细节,译文如下。


在Go 1.12版本里,Go垃圾收集器依然使用非分代的并发的三色标记清理算法。Ken Fox这篇文章里关于GC的动画非常赞。Go的垃圾收集器的实现随着Go版本的变化而发生变化。因此,一旦发布下一版本,很多细节可能会有不同。


垃圾收集器行为


Go垃圾收集器的行为分为两个大阶段Mark(标记)阶段和Sweep(清理)阶段。Mark阶段又分为三个步骤࿰

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值