设为首页 - 加入收藏 华夏网 ()- 华夏领先的王中王论坛资讯站长网站!
热搜: 北京 郑爽 医院
当前位置: 主页 > 红姐论坛 > 正文

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

发布时间:2019-08-14 03:22 所属栏目:[红姐论坛] 来源:William Kennedy
导读:首先强调下本文的起因是在高可用架构后花园群的一次聊天,大家在争论Golang的GC到底是类似Java的ZGC还是类似Java的CMS GC。我个人的看法是Golang的GC是类似于Java的CMS GC,官方的mgc的注释这么说的: //TheGCrunsconcurrentlywithmutatorthreads,istypeac

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

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

  1. // The GC runs concurrently with mutator threads, is type accurate (aka precise), allows multiple 
  2. // GC thread to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is 
  3. // non-generational and non-compacting. Allocation is done using size segregated per P allocation 
  4. // 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

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

  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写屏障就是这个原理,伪代码如下:

  1. writePointer(slot, ptr): 
  2.     shade(ptr) 
  3.     *slot = ptr 

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

  1. writePointer(slot, ptr): 
  2.     if (isGery(slot) || isWhite(slot)) 
  3.         shade(*slot) 
  4.     *slot = ptr 

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

  1. writePointer(slot, ptr): 
  2.     shade(*slot) 
  3.     if current stack is grey: 
  4.         shade(ptr) 
  5.     *slot = ptr 

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

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

【免责声明】本站内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。

网友评论
推荐文章