三色标记法(黑白灰)
根据可达性分析,从GC Roots开始进行遍历访问,按“是否访问过”这个条件标记成以下三种颜色:
黑色
:本对象已访问过,而且本对象 引用到 的其他对象 也全部访问过
了。 (自己标记完了,自己的孩子(包括多个孩子)也标记完了,都不是垃圾)
灰色
:本对象已访问过,但是本对象 引用到 的其他对象 尚未全部访问
完。全部访问后,会转换为黑色。(自己标记完了,自己的孩子(包括多个孩子)没有标记)
白色
:尚未访问过,对象即为GC Roots 不可达,可以进行回收。
::多标, 多标记了一些正常节点,实际上是需要回收的
即,已经标记好的引用,突然断裂。导致后面的引用链都是垃圾,而没进行回收。产生浮动垃圾
解决方案: 无法处理,等一个轮回处理。不影响结果
:: 漏标,漏标记了一些不正常的节点,实际是不需要回收的e
技巧💡
原本应该存活的对象标记为已消亡(漏标)这就回带来问题了,一个需要被引用的对象被回收了,那程序返回的结果一定是有问题的
条件:
- 变成孤立节点,原本的引用端口了。即灰色E断开了G
- 已经处理过的节点,重新引用。即黑色D重新引用G
转换代码即
var G = objE.fieldG;
objE.fieldG = null; // 灰色E 断开引用 白色G
objD.fieldG = G; // 黑色D 引用 白色G
只要在上面这三步中的任意一步中做一些“手脚”,将对象 G 记录起来,然后作为灰色对象再进行遍历即可。比如放到一个特定的集合,等初始的 GC Roots 遍历完(并发标记),该集合的对象遍历即可(重新标记)
读写屏障,其实就是指在赋值操作前后或者读值时,加入一些处理(可以参考AOP的概念)
写屏障 + SATB(G1)
当对象 E 的成员变量的引用发生变化时(objE.fieldG = null;),我们可以利用写屏障,将 E 原来成员变量的引用对象 G 记录下来:
写屏障 + 增量更新(CMS使用)
当对象 D 的成员变量的引用发生变化时(objD.fieldG = G;),我们可以利用写屏障,将 D 新的成员变量引用对象 G 记录下来。
增量更新(Incremental Update):就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来,等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次(重新标记阶段)。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了
读屏障 + 颜色指针 (ZGC):
因为条件二中【黑色对象重新引用了该白色对象】,重新引用的前提是:得获取到该白色对象,此时已经读屏障就发挥作用了。
这个动作是不是非常像JDK并发中用到的CAS自旋?读取的值发现已经失效了,需要重新读取。而ZGC这里是之前持有的指针由于GC后失效了,需要通过读屏障修正指针
与颜色指针的区别
三色标记法,将是否标记为垃圾,放在对象头中的gc标记中,而颜色指针是放在对象地址的指针上。