文章目录
三色标记法是JVM中用来标记对象是否为垃圾的一种方法,主要是针对CMS、G1等垃圾收集器使用的,这类收集器都有一个垃圾回收线程与用户线程同时执行的并发过程,这是一般标记清除算法不能支持的。
为什么要使用三色标记法?
三色标记法就是为了使垃圾扫描阶段能够与用户线程并发执行而产生的,因为传统的标记清除算法,必须要暂停所有用户线程。
传统的标记清除算法下,只有两个状态位0、1,比如0:表示未标识,1:表示对象可达,一次扫描后结束清除所有状态为0的,然后再把状态为1的重置为0。
但是如果与用户线程同时执行就不能这样玩了,因为一旦同时执行就有可能在一条链未全部扫描完的情况下,用户线程改变了这条链上的引用关系,比如现在有一条引用链:A—>B—>C—>D,当扫描到C时,A和B都被标识为1,此时C到D的引用关系被删除,那么D对象就不能确定是否为垃圾对象,因为有可能D又被用户线程设置为其他对象的引用了,那么为了D不被误删,只能让D的标识也为1,但是如果D就是没有被其他对象引用了,那么D就逃过了这次垃圾收集的过程,这就会造成大量的浮动垃圾。
当然肯定也不能设置为0,因为0在未扫描之前虽然表示的是未标记对象,但是在扫描开始后就表示垃圾对象了。
所以上述问题很明显就是缺少了一个表示中间状态的过程,由于线程同时进行,所以引用链上的对象并不是简单的可达与不可达的关系,而是会有一个扫描过程中的状态,所以就出现了三色标记法。
三色标记法中的三色
白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是 白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过,也就是整个引用链还未全部扫完。
黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
初始阶段:全部为白色
A对象扫描完成后变为黑色,B对象正在扫描则标识为灰色,剩余的白色对象标识还未被扫描。
最终按照可达性分析算法一轮扫描下来结果如下
最终白色对象E即为垃圾对象。
主要就是利用三个集合,分别来存放三种颜色的对象,开始扫描时把被扫描的白色对象从白色集合中移动到灰色集合中,灰色对象扫描完成后,又被移动到黑色对象集合中,最终完成所有初始标记时识别到的GCRoot引用链路径后,余下的白色集合中的对象即为垃圾对象。
三色标记的漏标问题
三色标记的思想非常简单,但仔细分析一下就会发现其中的问题,如果把一个白色对象的引用设置到一个黑色的对象上,那么这个白色对象就会被错误的认为是一个垃圾对象,因为黑色对象表示的是这个对象已经完成了扫描且这个对象的所有引用都已经扫描过。
第一次标记时,关系如下:
用户线程修改了引用关系如下:
此时接着扫描E对象,发现E对象之后没有引用关系了,把E对象设置为黑色,垃圾收集器认为两条引用链上的对象全部扫描完毕,但是F对象却被遗漏了。
Wilson于1994年在理论上证明了,当且仅当以下两个条件同时满足时,会产生“对象消失”的问 题,即原本应该是黑色的对象被误标为白色:
1、赋值器插入了一条或多条从黑色对象到白色对象的新引用;
2、赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
如何解决漏标问题?
既然问题的产生需要同时满足上述两个条件,那么要解决就只需破坏其中一种即可,CMS和G1恰好分别利用其中一种条件来解决。
CMS 增量更新(Incremental Update)
增量更新要破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象 了。
C对象被修改为灰色,那么就会沿着灰色对象继续扫描,最终会扫描到F对象。
G1 原始快照(Snapshot At The Beginning, SATB)
原始快照要破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。这也可以简化理解为,无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来 进行搜索。
第一、二两条链扫描完成后,多出了第三条引用链,从之前的灰对象E开始,指向F对象,这样F对象就不会被清理掉了。
使用这种方式会有一个问题,假设引用关系如下:
之后引用关系被改变
E到F的引用没有了,F也没有再被其他对象引用,但是由于E对象为灰色对象,所以为了避免漏标,E对象最终还是会有一条到F的引用关系,这就是浮动垃圾问题,F对象会逃过本次的垃圾扫描,等待下次再被清理,但这总比漏标要好的多。
对比文章一开始提到的标记清除方法下产生的浮动垃圾问题,这种情况要少很多,因为只有在改变灰色对象时才需要记录,而如果用标记清除那么只要在垃圾回收期间改变了对象的关系都必须定义为非垃圾对象。