三色标记、原始快照、增量更新相关
三色标记遍历过程(其实就是一个bfs的过程)
假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:
- 初始时,所有对象都在白色集合中;
- 将GCRoots直接引用到的对象挪到灰色集合中;
- 从灰色集合中获取对象:
- 将本对象引用到的其他对象全部挪到灰色集合中;
- 将本对象挪到黑色集合中;
- 重复步骤3,直至灰色集合为空时结束
- 结束后,仍在白色集合的对象即为GCRoots不可达,可以进行回收。
多标
多标问题指的是原本应该回收的对象,被多余地标记为黑色存活对象,从而导致该垃圾对象没有被回收。假设,在应用程序执行时,应用程序的本意就是删除D->E的引用,但是在遍历的过程中,D被加入到黑色节点集合,E被加入灰色节点集合了,这是切换到应用线程,删除了D->E的引用(本意就是这样,按照这样EFG都将不再被GCRoots引用,所以需要回收)。但是由于E已经被加入到了灰色集合,之后还会拿出来遍历,所以E还会被放到黑色集合中,F、G会被放到灰色集合。所以E、F、G都会被误认为是可达的,此次垃圾回收不会回收这些对象。
漏标
漏标问题指的是原本应该被标记为存活的对象,被遗漏标记为黑色,从而导致该垃圾对象被错误回收。漏标问题的发生必须满足两个充要条件:
- 至少有一个黑色对象新增了对白色对象的引用
- 所有灰色对象指向该白色对象的引用都断开了
对于漏标问题的解决,只需要破坏这两个条件之一即可,增量更新就是破坏第一个条件,原始快照破坏第二个条件。
- 增量更新:在增加黑色对象对白色对象的引用时,记录下黑色对象,在后续【重新标记】阶段再以黑色对象为根重新扫描。相当于以这些黑色对象为根,再BFS一遍,那原来的白色对象重新染成灰色。
- 原始快照:当灰色对象未完成对白色对象的扫描时,就断开了引用。这个时候白色对象就会被记录下来,之后在【最终标记】阶段再以白色对象为根,走一遍BFS扫描的过程,把原来的白色对象标记。(这会产生浮动垃圾的问题,就比如原先这个灰色对象指向白色对象的引用就是应用程序想断开的,但是被使用原始快照算法的垃圾收集器记录了下来,误以为是不该回收的。导致这当前阶段无法回收,只能在下次垃圾回收是回收。)
总结
- 原始快照的Remark阶段比增量更新块,因为增量更新要重新扫描新生代,原始快照只需要处理记录下的引用修改。
- 对于对象的消失的问题,原始快照和增量更新都能解决。
- 原始快照的浮动垃圾比增量更新多。
- 为什么要有三色标记算法? 因为传统的「标记 - 清除」算法效率太低,于是采用三色标记算法通过将对象分成白色、黑色、灰色,以及将整个过程拆分成「初始标记、并发标记、重新标记、并发清除」4 个过程,从而降低 GC 停顿时间。
参考:
https://blog.csdn.net/houjx3/article/details/118417561
https://blog.csdn.net/m0_71777195/article/details/126555456
https://blog.csdn.net/Yao_ziwei/article/details/117781372