业界常见的垃圾回收算法有以下几种:
- 引用计数:对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0时回收该对象。
- 优点:对象可以很快地被回收,不会出现内存耗尽或达到某个阀值时才回收。
- 缺点:不能很好地处理循环引用,而且实时维护引用计数,也有一定的代价。
- 代表语言:Python、PHP、Swift
- 标记-清除:从根变量开始遍历所有引用的对象,引用的对象标记为”被引用”,没有被标记的进行回收。
- 优点:解决了引用计数的缺点。
- 缺点:需要STW,即要暂时停掉程序运行。
- 代表语言:Golang(其采用三色标记法)
- 分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,而短的放入新生代,不同代有不同的回收算法和回收频率。
- 优点:回收性能好
- 缺点:算法复杂
- 代表语言: JAVA
一、Go V1.3之前的标记-清除(mark and sweep)算法
做法:暂停程序业务逻辑, 遍历引用对象,找出所有可达的对象,然后做上标记。第二步,回收未标记的对象。对象5,6被回收
主要缺点:
- STW,stop the world;让程序暂停,程序出现卡顿 (重要问题)。
二,Go V1.5的三色并发标记法
三色只是为了叙述上方便抽象出来的一种说法,实际上对象并没有颜色之分。这里的三色,对应了垃圾回收过程中对象的三种状态:
- 灰色:对象还在标记队列中等待
- 黑色:对象已被标记,gcmarkBits对应的位为1(该对象不会在本次GC中被清理)
- 白色:对象未被标记,gcmarkBits对应的位为0(该对象将会在本次GC中被清理)
注意,第一次扫描时,被引用的对象就会标记为灰色,意味着不会被清除了。第二次扫描会将灰色变成黑色并将其引用的对象也标记为灰色,递归直至没有灰色
注意:三色标记法是一定要依赖STW的. 因为如果不暂停程序, 程序的逻辑改变对象引用关系, 这种动作如果在标记阶段做了修改,会影响标记结果的正确性
可以看出,有两个问题, 在三色标记法中,是不希望被发生的
- 条件1: 一个白色对象被黑色对象引用(白色被挂在黑色下)
- 条件2: 灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)
当以上两个条件同时满足时, 就会出现对象丢失现象!
为了防止这种现象的发生,最简单的方式就是STW,直接禁止掉其他用户程序对对象引用关系的干扰,但是STW的过程有明显的资源浪费,对所有的用户程序都有很大影响,如何能在保证对象不丢失的情况下合理的尽可能的提高GC效率,减少STW时间呢?
答案就是, 那么我们只要使用一个机制,来破坏上面的两个条件就可以了.也就是
插入屏障 和 删除屏障(均为写屏障)
栈空间不触发屏障效果
强三色不变式 -- 插入屏障
理论:不存在黑色对象引用到白色对象的指针。
插入屏障: 在A对象引用B对象的时候,B对象被标记为灰色。(将B挂在A下游,B必须被标记为灰色)
注意:堆空间对象屏障,新创建/被引用的对象变为灰色。栈空间对象不会触发屏障,所以可能栈中还有未被回收的对象,故之后对栈进行STW暂停保护避免并发错误(又有新的被引用),重新对栈进行三色标记并发扫描(这里算是重复扫描了)
弱三色不变式 -- 删除屏障
理论:所有被黑色对象引用的白色对象都处于灰色保护状态.
删除屏障:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。
:这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。
三,Go V1.8的混合写屏障(hybrid write barrier)机制
插入写屏障和删除写屏障的短板:
-
插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;
-
删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。
Go V1.8版本引入了混合写屏障机制(hybrid write barrier),避免了对栈re-scan的过程,极大的减少了STW的时间。结合了两者的优点。
混合写屏障规则
具体操作
:
- GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW),
- GC期间,任何在栈上创建的新对象,均为黑色。
- 被删除的对象标记为灰色。(删除屏障)
- 被添加的对象标记为灰色。(插入屏障)
满足
: 变形的弱三色不变式.
Golang中的混合写屏障满足弱三色不变式
,结合了删除写屏障和插入写屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间。
总结
以上便是Golang的GC全部的标记-清除逻辑及场景演示全过程。
- GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。
- GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通
- GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。