目录
一、什么是三色标记
二、三色标记的过程
三、三色标记的缺点
四、垃圾回收机如何弥补三色标记的缺点
在CMS、G1这种并发的垃圾收集器收集对象时,假如一个对象A被GC线程标记为不可达对象,但是用户线程又将A对象标记为可达对象,那么此时直接对A对象做清除时那将会发生很严重的错误,那么对象A是如何摆脱被清除的命运呢?今天就简单来聊聊这个问题。
一、什么是三色标记
主流的垃圾收集器基本上都是基于可达性分析算法来判定对象是否存活的。根据对象是否被垃圾收集器扫描过而用白、灰、黑三种颜色来标记对象的状态的一种方法。而其中
- 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始阶段,所有的对象都是白色的,若在分析结束之后对象仍然为白色,则表示这些对象为不可达对象,对这些对象进行回收。
- 灰色:表示对象已经被垃圾收集器访问过,但是这个对象至少存在一个引用(属性)还没有被扫描过。
- 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经被扫描过。黑色表示这个对象扫描之后依然存活,是可达性对象,如果有其他对象引用指向了黑色对象,无须重新扫描,黑色对象不可能不经过灰色对象直接指向某个白色对象。
二、三色标记的过程
初始状态
初始阶段只有GC Roots是黑色的,其他对象都是白色的,如果没有被黑色对象引用那么最终都会被当做垃圾对象回收。
开始扫描
A和B均为扫描过的对象并且其引用也已经被垃圾回收器扫描过所以此时A、B对象均变为了黑色,而刚扫描到对象C,由于C的D和E还没有被扫描到,所以C暂时为灰色。
顺利扫描结束
此时扫描完成,黑色对象就是存活的对象,即可可达对象,白色对象G为不可达对象,在垃圾回收时就会被回收掉。
三、三色标记的缺点
我们知道CMS和G1等垃圾回收器是一个并发回收的垃圾回收器,上面的图片只是展示了一种理想状态下的三色标记,实际上在并发的情况下会存在多标和漏标的问题。
场景
假如GC线程已经扫描到了E对象,此时E对象为灰色,这个时候用户线程将C的引用E断开,那么GC就会认为E对象是可达对象,而不会对E进行垃圾回收,但实际上E是个垃圾对象,这个时候就会产生多标问题,多标问题其实还可以接受,E作为浮动垃圾,那么等到下次垃圾回收的时候回收掉。
场景
假如用户线程先断开了C到E的引用,那么E对象就认为是不可达对象,而此时B对象又引用了E对象,但是三色标记又不会重新从B点开始标记到E,那么E就会被认为是垃圾对象,但实际上E是有引用的,那么此时对E进行垃圾回收,之后就一定会产生错误,这就是漏标问题。
四、垃圾回收机如何弥补三色标记的缺点
上面演示了三色标记法存在的一些问题,那么如何解决这些问题呢。首先存在以上问题有两个条件同时满足才会发生
- 赋值器插入了一条或者多条从黑色对象到白色对象的引用。
- 赋值器删除了所有的从灰色对象到白色对象的直接引用或者间接引用。
那么要想解决并发扫描时对象消失的问题只需要破坏任何一个条件即可。因此产生了两种解决方案——增量更新(Incremental Update)和原始快照(Snapshot At The Beginning, SATB)。
增量更新破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用关系时。就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象作为根对象,再重新扫描一遍。比如漏标问题中,一旦B对象直接指向了E对象,那么在并发扫描之后,就会将B对象作为灰色对象,再重新扫描一遍。这样虽然避免了漏标问题,但是重新标记会导致STW的时间变长。
原始快照破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后再将这些记录过的引用关系中的灰色对象为根对象再重新扫描一遍。例如漏标问题的途中,C断开E的引用关系时会保存一个快照,然后等扫描结束之后,会把C当作根再重新扫描一遍,假如B没有引用E,那么E对象也会认为是可达对象,这样E就成了浮动垃圾,只能等下次垃圾回收时再回收。
无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。在HotSpot虚拟机中,CMS是基于增量更新来做并发标记的,G1、Shenandoah则是用SATB来实现的。