-
Notifications
You must be signed in to change notification settings - Fork 0
- gcTriggerAlways: 强制触发GC
- gcTriggerHeap: 当前分配的内存达到一定阈值时触发,这个阈值在每次GC过后都会根据堆内存的增长情况和CPU占用率来调整
- gcTriggerTime: 当一定时间没有执行过GC就触发GC(2分钟)
- gcTriggerCycle: runtime.GC()调用
是一个有限的指针集合,程序可不经过其他对象直接访问这些指针,堆中的对象被加载时,需要先加载根中的指针。在Go中,一般为goroutine自己的栈空间和全局栈空间。
本来是灰色的B指向C, 但是我们还没有扫描B的时候, C被改指向A, 但是A已经是黑色的了, 那么现在黑色的指向一个白色的对象且没有任何灰色的对象指向白色的对象. 这时白色的对象无论如何都不会被访问到.
当回收器满足下面两种情况之一时,即可保证不会出现对象丢失问题。
弱三色不变式:所有被黑色对象引用的白色对象都处于灰色保护状态(直接或间接从灰色对象可达)。 强三色不变式:不存在黑色对象到白色对象的指针。
强三色不变式很好理解,强制性的不允许黑色对象引用白色对象即可。而弱三色不变式中,黑色对象可以引用白色对象,但是这个白色对象仍然存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。
- 插入屏障拦截将白色指针插入黑色对象的操作,标记其对应对象为灰色状态,这样就不存在黑色对象引用白色对象的情况了,满足强三色不变式
- 删除屏障也是拦截写操作的,但是是通过保护灰色对象到白色对象的路径不会断来实现的。如上图例中,在删除指针e时将对象C标记为灰色,这样C下游的所有白色对象,即使会被黑色对象引用,最终也还是会被扫描标记的,满足了弱三色不变式
- [go 1.5]插入屏障: Dijkstra的插入写屏障在标记开始时无需STW,可直接开始,并发进行,但结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活. 对栈上指针的写入添加写屏障的成本很高, 所以Go选择仅对堆上的指针插入增加写屏障, 总是认为栈是灰色的.
- 删除屏障: Yuasa的删除写屏障则需要在GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象,但结束时无需STW. 但是快照会让一些对象多活过这一轮, 回收精度差.
- [go 1.12]混合屏障: 只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间。
R大的文章解决了三个认知问题
-
GO的GC算什么系列的, 其他语言有类似的实现吗?
跟Java的CMS GC很像. 都是基于增量的写屏障的保证下的并发标记清楚机制.
-
如果有那么他们的流程是什么样子的, 各自有哪些优化?
核心流程一样,GO还有很多优化(比如不需要全部扫描栈), 但是也少了在re-marking之前试图重新标记的优化
-
GO GC优势是什么, 劣势是什么?
最大限度的减少了
STW
, 但是会长时间占用CPU, 吞吐量上不去. 同时因为Go大量的struct都是创建在goroutine的栈上的, 所以创建新对象的数量是没有Java那么多的.
Go的CMS GC
其实与HotSpot VM
的CMS GC
相比是非常相似的。都是只基于incremental update
系write-barrier
的Mostly-Concurrent Mark-Sweep
。两者的工作流程中最核心的步骤都是:
- Initial marking:扫描根集合
- Concurrent marking:并发扫描整个堆
- Re-marking:重新扫描在(2)的过程中发生了变化/可能遗漏了的引用
- Concurrent sweeping 具体到实现,两者在上述核心工作流程上有各自不同的扩展/优化。
两者的(1)都是stop-the-world
的,这是两者的GC暂停的主要来源之一。 HotSpot VM的CMS GC的(3)也是stop-the-world
的,而且这个暂停还经常比(1)的暂停时间要更长;Go 1.6 CMS GC则在此处做了比较细致的实现,尽可能只一个个goroutine暂停而不全局暂停----只要不是全局暂停都不算在用户关心的"暂停时间"里,这样Go版就比HotSpot版要做得好了。
HotSpot版CMS对(3)的细化优化是,在真正进入stop-the-world的re-marking之前,先尝试做一段时间的所谓并发的"abortable concurrent pre-cleaning",尝试并发的追赶应用程序对引用关系的改变,以便缩短re-marking的暂停时间。不过这里实现得感觉还不够好,还可以继续改进的。
有个有趣的细节,Go版CMS在(3)中重新扫描goroutine的栈时,只需要扫描靠近栈顶的部分栈帧,而不需要扫描整个栈----因为远离栈顶的栈帧可能在(2)的过程中根本没改变过,所以可以做特殊处理;HotSpot版CMS在(3)中扫描栈时则需要重新扫描整个栈,没抓住机会减少扫描开销。Go版CMS就是在众多这样的细节上比HotSpot版的更细致。
再举个反过来的细节。目前HotSpot VM里所有GC都是分代式的,CMS GC在这之中属于一个old gen GC,只收集old gen;与其配套使用的还有专门负责收集young gen的Parallel New GC(ParNew),以及当CMS跟不上节奏时备份用的full GC。分代式GC很自然的需要使用write barrier,而CMS GC的concurrent marking也需要write barrier。HotSpot VM就很巧妙的把这两种需求的write barrier做在了一起,共享一个非常简单而高效的write barrier实现。 Go版CMS则需要在不同阶段开启或关闭write barrier,实现机制就会稍微复杂一点点,write barrier的形式也稍微慢一点点。
从效果看Go 1.6的CMS GC做得更好,但HotSpot VM的CMS GC如果有更多投入的话也完全可以达到一样的效果;并且,得益于分代式GC,HotSpot VM的CMS GC目前能承受的对象分配速度比Go的更高,这算是个优势。