什么是三色标记
JVM 中的垃圾回收是基于 标记-复制、标记-清除和标记-整理三种模式的,那么其中最重要的其实是如何标记,像Serial、Parallel这类的回收器,无论是单线程标记和多线程标记,其本质采用的是暂停用户线程进行全面标记的算法,这种算法的好处就是标记的很干净,而且实现简单,缺点就是标记时间相对很长,导致STW的时间很长。
那么后来就有了并发标记,适用于CMS和G1,并发标记的意思就是可以在不暂停用户线程的情况下对其进行标记,那么实现这种并发标记的算法就是三色标记法,三色标记法最大的特点就是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个GC。
三色为那三色?
- 白色:尚未被GC访问过的对象,如果全部标记已完成依旧为白色的,称为不可达对象,既垃圾对象。
- 黑色:本对象已经被GC访问过,且本对象的子引用对象也已经被访问过了。
- 灰色:本对象已访问过,但是本对象的子引用对象还没有被访问过,全部访问完会变成黑色,属于中间态。
两个问题
1. 多标-浮动垃圾
一个本应该是垃圾的对象被视为了非垃圾,它的影响并不会很大,因为哪怕此次不会被回收下一次也会被回收
2. 漏标-读写屏障
一个本应该不是垃圾的对象被视为了垃圾,如果误清理了正在被使用的对象,那肯定会出现问题。那么如何解决这个问题呢?
出现这个问题的主要原因是,一个对象从被B引用,变更为了被A引用。那么对于A来说就是多了一个直接引用,对于B来说就是少了一个直接引用。我们可以从这两个方面入手来解决这个问题,对应了也有两个方案,分别是增量更新(Incremental Update) 和原始快照(SATB,Snapshot At The Beginning)。
在这讲述解决方案之前,要描述两个名词:读屏障和写屏障。注意,这里的屏障和并发编程中的屏障是两码事儿。这里的屏障很简单,可以理解成就是在读写操作前后插入一段代码,用于记录一些信息、保存某些数据等,概念类似于AOP。
2.1 增量更新
增量更新是有重新标记的过程的,当黑色对象新增一个白色对象的引用时,就通过写屏障将这个引用关系记录下来。然后在重新标记阶段,再以这些引用关系中的黑色对象为根,再扫描一次,以此保证不会漏标。
要实现也很简单,在重新标记阶段直接把A对象(和其它有相同情况发生的对象)变为灰色,放入队列中,再来一次枚举过程。要注意,在重新标记阶段如果用户线程还是继续执行,那么这个GC永远可能也做不完了,所以重新标记需要STW,但是这个时间消耗不会太夸张。如果实在重新标记阶段耗时过长,那么可以尝试在重新标记之前做一次Minor GC,这个在CMS垃圾收集器中有介绍。
2.2 原始快照
原始快照是站在减少引用的对象(也就是例子中的B对象)的角度来解决问题。所谓原始快照,就是在赋值操作之前添加了写屏障,在进行操作之前会记录对象引用,记录下来的这个对象就可以称为原始快照。在记录下来之后会直接将它变为黑色,标为不需要处理,在实际清理的时候如果有对象引用它则正常,如果没有则为浮动垃圾,在下一次回收时会清除掉,但是此方案会产生较多的浮动垃圾。
G1使用的是原始快照,CMS使用的是增量更新。