• 【JVM】三色标记法


    原因

    在CMS等并发收集器,并发标记的过程中需要对对象进行标记,用于区别对象。防止多标,漏标等情况。

    三色标记

    三色标记法就是指将GC roots可达性算法分析遍历对象过程中将各个对象,按照”是否访问过“标记成不同的三种颜色(可以理解为类似于成员变量)。

    在这里插入图片描述

    • 黑色
      表示对象已经被垃圾收集器访问过(扫描过),并且这个对象的所有引用都已经被扫描过,它是安全存活的(不是垃圾对象),如果其他对象引用指向了黑色对象,是无需重新扫描的,黑色对象不可能直接(不经过灰色对象)指向白色对象
    • 灰色
      表示对象已经被垃圾收集器访问过,但是这个对象上至少存在一个引用还没有被扫描过。(表示正在扫描该对象的引用)
    • 白色
      表示对象尚未被垃圾收集器访问过,在可达性分析刚刚开始时的阶段,所有的对象都是白色的,如果在分析结束的阶段,还是白色的对象,表示不可达(被清理)。

    存在问题与解决方法

    多标(浮动垃圾)

    浮动垃圾并不会影响垃圾回收的正确性,可以等到下一轮垃圾回收中再清除。

    原因:

    • 并发标记过程中,由于用户线程运行导致部分局部变量(GC root)被销毁,导致该局部变量(GC root)所引用的对象(被扫描过,黑色)成为浮动垃圾。
    • 并发标记或并发清理,由于用户线程运行产生的新对象,通常被直接全部标记为黑色,而这些对象这此期间也会变成垃圾,算是浮动垃圾的一部分。

    解决:

    • 本轮GC将不会回收这部分内存,浮动垃圾并不会影响垃圾回收的正确性,只需要等到下一轮GC才被清除

    漏标(读写屏障)

    漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决.

    漏标原因(二者必须同时存在时,才会漏标):

    • 当黑色对象被插入新的指向白色对象的引用关系时,此时将会产生不扫描新插入的白色对象。(使用白色对象
    • 当有灰色对象的白色对象的引用被删除时,无法感知白色是否被其他对象引用。(产生白色对象

    注意:白色对象只会产生于灰色对象引用关系中,这就导致黑色对象关联的白色对象一定来自灰色对象,而新new出来的对象会被直接标记为黑色

    解决方法:

    所以漏标可以在产生白色对象的地方制止,也可以在使用白对象的地方制止。由此产生了两种方法。

    • 增量更新(Incremental Update)
    • 原始快照(Snapshot At The Beginning或SATB)写屏障
    增量更新

    针对白色对象的使用方

    当黑色对象插入新的指向白色对象的引用关系时,将新插入的引用关系记录下来,等并发扫描结束之后,再将记录过的黑色对象为根,重新扫描一次。

    保证在扫描时线程正在扫描灰色对象引用的对象时,黑色对象引用了白色对象导致漏标问题

    简单理解:黑色对象被新插入了指向白色对象的引用后,就变成灰色对象

    原始快照(SATB)

    针对白色对象的产生方

    当灰色对象删除指向白色对象的引用关系时,将要删除的引用关系记录下来(记录灰色对象指向白色对象的关系),在并发扫描结束后。再将记录过的引用关系的灰色对象为根,重新扫描一次(此时是使用之前保存的关系进行扫描的,即还原到未删除时候的快照,扫描的是各种被删除的对象),扫描到白色对象再将白色对象直接标记为黑色。通常使用写屏障实现的。

    保证在扫描时线程正在扫描灰色对象引用的对象时,灰色对象的引用改变导致某些白色对象不可达(用户线程正在运行)

    注意: 标记为黑色,该对象就会在本轮gc中存活下来,下一轮gc重新扫描,这个对象就有可能是浮动垃圾。

    写屏障

    在底层代码中进行类似AOP切面的代码,即在赋值前后进行操作。

    写屏障实现SATB

    当对象的成员变量的引用发生变化时,例如删除引用(赋值为null),我们可以利用写屏障,将原有成员变量的引用记录下来

     void pre_write_barrier(oop* field) 
     { 
      oop old_value = *field; // 获取旧值 
      remark_set.add(old_value); // 记录原来的引用对象 
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    写屏障实现增量更新

    当对象的成员变量的引用变化,例如新增引用(将之前为null值赋值),就可以利用写屏障,将新的成员变量引用对象记录下来。

    读屏障

    与写屏障类似,即在读值前后进行操作。

    读屏障更加直接,当读取成员变量时,就一律记录下来。

    void pre_load_barrier(oop* field) {
    oop old_value = *field;
    remark_set.add(old_value); // 记录读取到的对象
    }
    
    • 1
    • 2
    • 3
    • 4

    结语

    现代追踪式(可达性分析)的垃圾回收器,几乎都借鉴了三色标记法的算法思想,实现的方式也都差不多。比如白色/黑色 集合一般都不会出现(但是有其他体现颜色的地方)、灰色集合可以通过栈/队列/缓存日志等方式进行实现、遍历方式可 以是广度/深度遍历等等。

    读写屏障在Java Hotsot VM中,并发标记时对漏标处理方案:

    • CMS:写屏障+增量更新
    • G1,Shenandoah:写屏障+SATB
    • ZGC:读屏障
      工程实现中,读写屏障还有其他功能,比如写屏障可以用于记录跨代/区引用的变化,读屏障可以用于支持移动对象的并 发执行等。功能之外,还有性能的考虑,所以对于选择哪种,每款垃圾回收器都有自己的想法。

    为什么G1用SATB?CMS用增量更新?

    我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描 被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代 区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC 再深度扫描。

  • 相关阅读:
    Verilog 过程连续赋值
    【最详细最全】Linux启动nginx
    永磁同步电机转子位置估算专题——正交锁相环
    深入理解深度学习——Transformer:基础知识
    Linux Centos7 下使用yum安装的nginx平滑升级
    什么是身份证ocr识别?身份证ocr识别接口API能干什么?
    Redis群集
    sqlyog导入csv失败的解决方法
    C++ opencv 图像色彩空间转换--色域捕获
    WPF开发分页控件:实现可定制化分页功能及实现原理解析
  • 原文地址:https://blog.csdn.net/at10090/article/details/121737131