三色标记算法 SATB
用处
G1在并行标记垃圾的时候会使用这个算法
主要是参考这个视频的听译
算法描述
将所有对象分为三种颜色
- 白色:没有检查
- 灰色:自身被检查了,成员没被检查完(可以认为访问到了,但是正在被检查,就是图的遍历里那些在队列中的节点)
- 黑色:自身和成员都被检查完了
这是一个中间状态
根节点都是黑的(自己检查了,成员也都检查完了)
中间有些灰的
还有一些没被检查到的白的
把灰色的对象都放到队列中
然后从队列中取出一个灰色对象,假设是下图中的红色圈住的这个灰色对象
然后我们检查他的所有成员对象,并把那些变成灰色,放到队列中,自己变成黑色(检查完了)
再举个例子,再从队列中取出来的是上面那连续两个灰色的对象,然后检查他的孩子,孩子变灰,放入队列,自身变黑
这些叶子节点,指向null,或者指向基本类型(int, long),所以他们也黑了
所以就发现剩下那些白的,那就是垃圾了(无法到达)
注意,从来不会有黑色对象指向白色对象,NEVER!
- 黑色对象会指向灰色对象,代表正在处理灰色对象
- 灰色对象会指向白色对象,代表还没检查白色对象
- 但是绝不会有黑色对象指向白色对象
- 黑色对象代表自己的孩子都检查过了,所以黑色对象的孩子不可能是白的(白色没被检查过)
However!
别忘了在我们标记的时候,Application还在运行着呢,他可能会修改这个图的结构,引入一个问题:Lost Object Problem (漏标)
想象以下情况
我们正在遍历图
队列中有B这个灰色对象
然后我们的标记线程stop了
Application线程开搞 写入 A.c = C
Application线程 写入 B.c = null
OK,这确实是Application可以干的事情????
然后我们回到了垃圾收集线程,从队列中拿出B这个灰色对象,发现B没有孩子了,ok标成黑色的吧
这就有问题了
因为C现在是白的,我们会认为他是垃圾,然后回收
但是实际上他还有A引用呢
那么我们回收C然后修改这块内存,就可能导致JVM crash了
怎么解决呢?
可以看到,发生这种bug的情况需要以下两个条件同时发生
1.灰色不再指向白色
2.黑色指向这个白色
所以,要解决这个问题,要么就从这两个条件入手
CMS是从条件2入手搞的;G1是从条件1入手搞的
为了防止上面说的那种情况,G1就说,那行吧,我们就继续认为C是活着的吧,别把他给回收了(当然也可能C真的是垃圾而没被回收,产生了floating garbage,不过也没关系,因为下次回收,如果C真的没人引用了,我们还是能把C给回收了)
具体怎么搞?
又用到我们的write barrier(JVM插入一小段代码到我们的应用程序中)
每次有指向空(B.c = null)都会运行这一小段代码(记录下这种边角情况,放到队列中,后面remark阶段处理)
SnapshotAtTheBeginning(SATB)
- 在初始标记阶段保存对象图
- 遇到C这种情况,把他放到队列中,在remark阶段处理
- 可能会产生floating garbage,在下一个周期回收