目录
1.三色标记算法是什么
三色标记算法是一种常见的垃圾收集的标记算法,属于根可达算法的一个分支,垃圾收集器CMS,G1在标记垃圾过程中就使用该算法。
1.1 什么是三色
三色指的是白色,灰色,黑色(white,gray,black)。抽象的理解,可以认为垃圾收集器在标记阶段将对象染成不同的颜色,根据不同的颜色做不同处理。那么三种染色的对象分别指什么呢?
白色
1- 在标记开始时,堆内存中的对象都是白色的。
2- 在标记结束时,仍然是白色的对象,将会被视为已死的对象而被清除。
灰色
灰色是由白色标记成为灰色,表示该对象Obj是根可达对象(存活的对象,不会被清理),但Obj的所有引用的对象还没被垃圾收集器访问;灰色是一个过渡色,最终都会被标记为黑色。
黑色
黑色是由灰色标记成为黑色,表示该对象Obj以及Obj的所有下级引用对象都已经被垃圾收集器访问并标记,Obj不会被垃圾收集器再次访问以查看是否有引用对象;此时Obj是黑色(存活的对象,不会被清理),Obj的所有引用的对象被标记为灰色。
总结:
三种颜色是递进的,最初所有对象颜色都是白色,对象Obj被根对象引用 当Obj被垃圾收集器访问后会被标记为灰色,当Obj的所有属性被访问标记(白色-->灰色)后Obj会被标记为黑色。
2.三色标记算法的过程
1,堆中所有的对象都是白色的。
2,根对象(GC Root set)引用的对象被垃圾收集器标记为灰色(图中A,F对象)
3, 垃圾收集器扫描灰色对象A和对象F的引用对象,将所有A的引用对象标记为灰色(B,C,D对象),将A对象标记为黑色;灰色对象F无引用,直接将对象F标记为黑色。
4,垃圾收集器扫描灰色对象B,C,D,发现B,C,D没有引用其他对象,将B,C,D标记为黑色。整个堆内存中没有灰色对象,A,B,C,D,F为黑色(存活对象),E,G,H为白色(已死对象),扫描标记完毕。
5,对象E,G,H在三色标记完毕后还是白色,代表对象E,G,H是已死的对象,直接进行清除,回收已死对象所占内存。
3.三色标记算法的实现原理
三色标记算法是由集合set实现的。三个集合分别为白,灰,黑集合;
3.1 三色集合
白色集合
白色集合是已死对象的集合,集合内的对象在标记环节结束后将被清除回收。
灰色集合
灰色集合,灰色对象的集合,灰色的对象代表其被根可达的黑色对象引用,但是还没有扫描该灰色对象是否引用白色集合中的对象;因为灰色对象根可达,它不能被回收,而且所有灰色的对象在被垃圾回收器扫描后将被迁移到黑色集合。
黑色集合
黑色集合,黑色对象的集合,黑色对象代表其根可达,是存活的对象,不可以被回收;黑色对象没有引用指向白色对象,只能指向灰色对象
3.2 三色标记算法实现
在三色标记的过程,其集合内部的变化如下列图,右侧所示
1,所有对象都是白色对象,放入白色集合。灰,黑集合此时为空
2,被根对象引用的对象标记为灰色,并将灰色对象从白色集合中转移到灰色集合。
3,垃圾收集器扫描灰色集合A,F对象,将A引用的对象B,C,D标记为灰色,转移到灰色集合中,A的所有引用对象都已扫描完毕,将A标记为黑色,转移到黑色集合中;F没有引用对象,标记为黑色,转移到黑色集合中。
4,垃圾收集器扫描灰色集合B,C,D对象,B,C,D对象没有引用对象,标记为黑色,转移到黑色集合中。灰色队列已经空了,扫描完毕;黑色集合为根可达对象集合,其内部对象均为存活对象,白色集合为根不可达对象集合,其内部对象均为已死对象。
5,清除白色集合内的已死对象E,G,H,实现内存回收。至此垃圾回收周期结束。
3.3 三色标记算法提炼
1,从灰色集合中取出一个对象Obj。
2,将对象Obj引用的对象移动到灰色集合,以确保Obj以及Obj引用的对象不被回收。
3,将对象Obj移动到黑色集合。
4,重复以上三步,直到灰色集合为空。
因为所有不能根可达的对象被加入到白色集合,而且对象只能够从白色集合转移到灰色集合,从灰色集合转移到黑色集合,所以不可能有黑色对象引用指向白色对象。
4.三色标记算法因为什么而出现
三色标记出现前,垃圾回收使用的算法是标记-清除算法,该算法显著特点就是整个垃圾回收周期中,用户的所有线程都会停止,也就是STW(stop the world,整个用户线程完全停止运行),非常不友好。
三色标记算法优化了标记-清除算法的STW,将一个相对大的STW分解成多个小的STW,做到了用户线程和垃圾回收的并发处理(这里并发也并非绝对并发,毕竟还存在少量的STW),减少了垃圾回收过程中STW的时间。
5.三色标记算法的漏洞
三色标记算法因为存在并发标记过程,这会导致出现多标,漏标问题;
什么是多标和漏标呢?
多标漏标都有一个【标】字,就是标记的意思,抽象的说标记等同于染色,对象最初都是白色(未来要被清除的),一旦染色,无论白->灰或灰->黑,对象就是根可达对象(存活对象),染色就是让对象存活,同样标记就是让对象存活的意思。
理解了【标】字代表让对象存活的意思,我们看多标,漏标就很好理解了,多标:多余标记,应该回收的对象,让它存活了,概括为让对象多存活了一会;漏标:遗漏/忘记标记,应该存活的对象,被回收了,概括为忘记让对象存活了。
5.1 多标
多标:多余标记,应该回收的对象让它存活了,概括让对象多存活一会,多标会产生浮动垃圾。
5.1.1多标怎么发生的呢?
在并发标记阶段,垃圾收集器线程和用户线程同时运行,垃圾收集器线程在扫描完A,D对象之后,如果用户线程此时执行代码 D.E = null; 那么黑色对象D对灰色对象E的引用就会断开,灰色对象E已不是根可达对象,按说应该是白色,但是已经多余标记成了灰色,对象E,F,G最终会被标记成黑色,而不能被垃圾收集器清除,成为浮动垃圾。
5.1.2多标问题如何解决?
浮动垃圾对系统影响不大,无需干预,因为下次垃圾回收周期会把它们清除掉。
5.2 漏标
漏标:遗漏/忘记标记,应该存活的对象,被回收了,概括为忘记让对象存活了
漏标可是一个严重的问题!一个活生生的对象,突然就被回收啦,凡用该对象的地方都会报空指针异常
5.2.1漏标是怎么发生的呢?
在并发标记阶段,垃圾收集器线程和用户线程同时运行,垃圾收集器线程在扫描完对象E之后,如果用户线程此时执行代码 E.G = null; D.G = G;这时对象E和对象G之间的引用断开,对象D引用对象G。三色标记算法中黑色对象表明了其所有的引用的对象都已被扫描并标记为灰色,并且不会再次扫描黑色对象的下级引用,这样会导致一个结果:虽然对象G根可达,但是它不会被扫描和被标记为灰-黑色,标记环节结束时,会把对象G当做垃圾清除掉
5.2.2漏标问题如何解决?
漏标问题的发生必须满足两个充要条件:
1,至少有一个黑色对象新增了对白色对象的引用
2,所有灰色对象指向该白色对象的引用都断开了
如上图所示,条件1,2均被满足,必然发生漏标问题。
1),黑色对象D新增了对白色对象G的引用 (满足条件1)
2),灰色对象E指向白色对象G的引用被断开了 (满足条件2)
条件2要满足所有指向该白色对象G的引用都断开,因为只有一个灰色对象D引用白色对象G,只需要断开这一条引用即可满足条件2.
因为必须满足两个充要条件,才会发生漏标的问题,所以反向推理,如果破坏任何一个条件,这个白色对象就不会被漏标。
5.2.2.1 增量更新方案
增量更新破坏了第一个条件:「至少有一个黑色对象新增了对白色对象的引用」,在并发标记阶段,黑色对象D指向了白色对象G,这时会把黑色对象D记录下来,在重新标记阶段,会把黑色对象D标记为灰色对象D,然后以灰色对象D为根节点,扫描整个引用链,白色对象G就会被依次标记为灰色、黑色,白色对象G漏标的问题就解决了。
增量更新的缺点:会重新扫描以黑色对象D为根节点的整个引用链,会浪费多一点时间。
CMS垃圾收集器使用增量更新方案。
5.2.2.2 原始快照方案
原始快照破坏了第二个条件:「所有灰色对象指向该白色对象的引用都断开了」,在并发标记阶段,灰色对象E断开了对白色对象G的引用,这是会把白色对象G记录下来,在最终标记阶段,会把白色对象G标记为灰色,然后以灰色对象G为根节点,扫描整个引用链,如此以来原来的白色对象G就会被依次标记为灰色、黑色,白色对象G漏标的问题就解决了。
原始快照的缺点:如果没有黑色对象指向白色对象,白色对象就变成了浮动垃圾,等下次GC回收
G1垃圾收集器使用原始快照方案。
6.三色标记使用场景
6.1 三色标记在垃圾收集器CMS中使用
1)初始标记(CMS initial mark),根节点直接引用的对象标记为灰色(短暂的STW)
2)并发标记(CMS concurrent mark)扫描灰色对象引用的对象并标记为灰色,原灰色对象标记为黑色
3)重新标记(CMS remark)增量更新方案矫正并发标记阶段的错误(短暂的STW)
4)并发清除(CMS concurrent sweep)清除白色的对象,内存回收
注:1)3)初始标记,重新标记都是短暂的STW,用户所有线程都停止,等待标记完成。
2)4)阶段都是并发操作,用户线程和垃圾收集器线程同时运行,不会STW。
6.2 三色标记在垃圾收集器G1中使用
1)·初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象
2)·并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析
3)·最终标记(Final Marking):原始快照方案处理并发阶段结束后仍遗留SATB记录
4)·筛选回收(Live Data Counting and Evacuation):根据用户期望的停顿时间来制定回收计划
注:1)3)4)初始标记,最终标记,筛选回收都存在STW,用户线程都停止。
2)阶段都是并发操作,用户线程和垃圾收集器线程同时运行,不会STW。
参考:
https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking
https://www.cnblogs.com/tyhA-nobody/p/17578936.html
书籍:深入理解Java虚拟机:JVM高级特性…佳实践(第3版)周志明