JVM 的三色标记法
JVM 的三色标记法
三色标记法是垃圾回收中的一种重要算法,它通过将对象分成三种颜色(黑色、灰色、白色)来标记对象的存活状态。
黑色:表示对象已经被垃圾收集器访问过并且从这个对象出发的所有引用都已经标记过。 如果一个新的标量指向一个黑色对象是不需要重新扫描一遍。黑色对象不可能直接指向白色对象(跳过灰色对象)
灰色:表示对象已经被垃圾收集器访问过 但是至少存在1条以上的引用未被扫描过
白色:表示未被扫描的对象。显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达
核心逻辑
初始阶段:所有对象都是白色,表示未被标记
灰色标记阶段:从根对象开始,将所有可达的对象标记为灰色,加入灰色对象列表
黑色标记阶段:遍历灰色列表,将每个灰色对象引用的对象也标记为灰色,重复;如果这个对象出发的所有引用都已经标记过,标记为黑色
清理阶段:此时的白色对象即为不可达对象,清理所有白色对象。
优势
有效处理循环引用:算法只关心可达性,可以正确处理对象之间的循环引用问题
提高垃圾回收效率:优化标记过程,提高效率
问题
多标
描述:
原本应该被回收的对象,被错误的标记成存活对象,从而导致对象没有被回收
产生原因:
在并发标记阶段,用户线程和GC线程同时运行,可能会导致对象的引用关系发生变化。
例如,假设对象E已经被标记为灰色(表示已经被访问过,但其引用的对象尚未全部访问),此时用户线程执行了objD.fieldE = null,断开了D对E的引用。按照正常逻辑,E及其引用的对象(如F、G)应该是不可达的,应该被回收。但由于E已经被标记为灰色,它仍然会被当作存活对象继续遍历,最终E、F、G等对象都会被标记为黑色(表示存活),从而不会被回收。
影响:
多标问题会导致内存中产生浮动垃圾,即那些本应该被回收但未被回收的内存。这些浮动垃圾不会影响程序的正确性,但会占用内存空间,直到下一次垃圾回收时才可能被清理。
解决:
多标问题可以在重新标记阶段进行修正。重新标记阶段会暂停用户线程,对可能存在问题的对象进行重新扫描和标记,从而确保正确性。
漏标
描述:
漏标问题是指原本应该被标记为存活的对象,却被错误地标记为垃圾对象,从而导致这些对象被错误地回收
产生原因:
漏标问题主要发生在并发标记阶段,此时用户线程和GC线程同时运行,可能会导致对象的引用关系发生变化。具体来说,漏标问题需要满足以下两个充要条件:
至少有一个黑色对象在自己被标记之后指向了这个白色对象:这意味着在并发标记阶段,某个已经被标记为黑色的对象(表示它及其引用的对象都已经被扫描过),又新增了一个引用指向了一个原本未被扫描的白色对象。
所有灰色对象在自己引用扫描完成之前删除了对白色对象的引用:这意味着在并发标记阶段,某个灰色对象(表示它已经被扫描过,但它的引用对象尚未全部扫描)取消了对某个白色对象的引用。
当这两个条件同时满足时,原本应该被标记为存活的白色对象就会被漏标,最终被错误地回收。

影响:
导致原本应该存活的对象被错误地回收,JVM错误。
解决:
为了解决漏标问题,不同的垃圾回收器采用了不同的策略:
CMS回收器的解决方案:增量更新(Incremental Update)
原理:当一个黑色对象新增了对一个白色对象的引用时,CMS回收器会记录下这个黑色对象。在重新标记阶段,将这个黑色对象重新标记为灰色,并重新扫描它的引用链。
优点:可以确保所有存活对象都被正确标记,不会产生漏标问题。
缺点:需要重新扫描整个黑色对象的引用链,效率较低。
G1回收器的解决方案:原始快照(Snapshot-At-The-Beginning, SATB)
原理:当一个灰色对象取消了对一个白色对象的引用时,G1回收器会记录下这个引用。在重新标记阶段,将这个白色对象重新标记为灰色,并从这个白色对象开始扫描它的引用链。
优点:不需要重新扫描整个黑色对象的引用链,效率较高。
缺点:可能会产生浮动垃圾,即一些本应该被回收的对象在本次GC中存活下来,需要在下一次GC中才能被回收。

浙公网安备 33010602011771号