在CMS等并发收集器,并发标记的过程中需要对对象进行标记,用于区别对象。防止多标,漏标等情况。
三色标记法就是指将GC roots可达性算法分析遍历对象过程中将各个对象,按照
”是否访问过“标记成不同的三种颜色(可以理解为类似于成员变量)。

浮动垃圾并不会影响垃圾回收的正确性,可以等到下一轮垃圾回收中再清除。
原因:
局部变量(GC root)被销毁,导致该局部变量(GC root)所引用的对象(被扫描过,黑色)成为浮动垃圾。并发标记或并发清理,由于用户线程运行产生的新对象,通常被直接全部标记为黑色,而这些对象这此期间也会变成垃圾,算是浮动垃圾的一部分。解决:
本轮GC将不会回收这部分内存,浮动垃圾并不会影响垃圾回收的正确性,只需要等到下一轮GC才被清除。漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决.
漏标原因(二者必须同时存在时,才会漏标):
使用白色对象)产生白色对象)注意:白色对象只会产生于灰色对象引用关系中,这就导致黑色对象关联的白色对象一定来自灰色对象,而新new出来的对象会被直接标记为黑色
解决方法:
所以漏标可以在产生白色对象的地方制止,也可以在使用白对象的地方制止。由此产生了两种方法。
写屏障针对白色对象的使用方
当黑色对象插入新的指向白色对象的引用关系时,将新插入的引用关系记录下来,等并发扫描结束之后,再将记录过的黑色对象为根,重新扫描一次。
保证在扫描时线程正在扫描灰色对象引用的对象时,黑色对象引用了白色对象导致漏标问题
简单理解:黑色对象被新插入了指向白色对象的引用后,就变成灰色对象
针对白色对象的产生方
当灰色对象删除指向白色对象的引用关系时,将要删除的引用关系记录下来(记录灰色对象指向白色对象的关系),在并发扫描结束后。再将记录过的引用关系的灰色对象为根,重新扫描一次(此时是使用之前保存的关系进行扫描的,即还原到未删除时候的快照,扫描的是各种被删除的对象),扫描到白色对象再将白色对象直接标记为黑色。通常使用写屏障实现的。
保证在扫描时线程正在扫描灰色对象引用的对象时,灰色对象的引用改变导致某些白色对象不可达(用户线程正在运行)
注意: 标记为黑色,该对象就会在本轮gc中存活下来,下一轮gc重新扫描,这个对象就有可能是浮动垃圾。
在底层代码中进行类似AOP切面的代码,即在赋值前后进行操作。
当对象的成员变量的引用发生变化时,例如删除引用(赋值为null),我们可以利用写屏障,将原有成员变量的引用记录下来
void pre_write_barrier(oop* field)
{
oop old_value = *field; // 获取旧值
remark_set.add(old_value); // 记录原来的引用对象
}
当对象的成员变量的引用变化,例如新增引用(将之前为null值赋值),就可以利用写屏障,将新的成员变量引用对象记录下来。
与写屏障类似,即在读值前后进行操作。
读屏障更加直接,当读取成员变量时,就一律记录下来。
void pre_load_barrier(oop* field) {
oop old_value = *field;
remark_set.add(old_value); // 记录读取到的对象
}
现代追踪式(可达性分析)的垃圾回收器,几乎都借鉴了三色标记法的算法思想,实现的方式也都差不多。比如白色/黑色 集合一般都不会出现(但是有其他体现颜色的地方)、灰色集合可以通过栈/队列/缓存日志等方式进行实现、遍历方式可 以是广度/深度遍历等等。
读写屏障在Java Hotsot VM中,并发标记时对漏标处理方案:
我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描 被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代 区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC 再深度扫描。