垃圾收集就意味着需要进行可达性算法,但是在执行可达性算法时,当前的 JVM 内部需要保持一个禁止的状态,也就是其他的用户线程不允许进行操作,所有的用户线程需要等到垃圾收集结束后才能开始继续执行,这意味着用户的体验感可能会非常地差:用户可能正在访问某个页面时,JVM 突然开始垃圾回收,如果当前 Java 堆内对象较少,那用户可能会怀疑是自己的网络原因,但是如果已经达到了十分庞大的对象量,用户就会看着加载页面转啊转,甚至弹出页面暂时无法访问。这个时候并发处理就十分重要了。
并发的可达性分析算法 - 三色标记
在三色标记算法中,对象会被标记为三种颜色之一:
- 白色:表示对象尚未被访问,可能是垃圾。
- 灰色:表示对象已被访问,但其引用的对象尚未全部访问。
- 黑色:表示对象及其所有引用的对象都已访问。
但是如果是在并发地进行并发的可达性算法时,用户线程突然对某个将要被删除、并且已经被标记了的对象进行了操作,我们假设所有对象都没有重写 finalize (毕竟 Java 官方不推荐使用),那么这个对象可能刚好被引用,但是下一秒就又被删除了,这样子导致错误。
https://shipilev.net/talks/javazone-Sep2018-shenandoah.pdf
Wilson 在 1994 年在理论上证明了,当且仅当以下两种条件都满足时,会产生"对象消失"问题
- 赋值器插入了一条或多条从黑色对象到白色对象的引用
- 赋值器删除了全部从灰色对象到白色对象的直接或间接引用
即:当前对象还未被扫描,当前对象被已经被扫描过并且标记了不会有其他引用对象的对象引用
因此,想要解决并发扫描时对象消失问题,只需要破坏这两种条件的其中一种即可
- 增量更新
- 原始快照
增量更新
增量更新破坏的是 "赋值器插入了一条或多条从黑色对象到白色对象的引用" 条件
当对黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,在并发扫描后,再将这些记录过的引用关系中的黑色对象为根,再重新扫描一次,可以简单理解为:只要黑色对象插入新的指向白色对象的引用,黑色对象就会变成灰色对象
原始快照
当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。可以简单理解成:无论引用关系删除与否都会按照刚刚开始扫描的那一刻的图像快照来进行搜索
简单来说,增量更新和原始快照都在一次可达性算法过后对更新过的节点进行扫描以达到不会产生"对象消失"的目的