Java垃圾收集器(二)

文章介绍了CMS垃圾收集器采用的三色标记算法来解决并发标记过程中的多标和漏标问题,包括浮动垃圾的概念以及解决漏标的读写屏障技术。同时,文章提到了记忆集和卡表在提高新生代GC效率中的作用,通过记录集避免了对整个老年代的扫描。
摘要由CSDN通过智能技术生成

1.三色标记算法 

上期说到了CMS垃圾收集器,它在并发标记和并发清理的过程中,对象的引用可能发生变化,多

标和漏标的情况就可能发生。所以CMS引用了三色标记算法来解决这个问题。

把gcroot可达性分析遍历对象按照是否访问过分为以下三种颜色:

黑色:表示已经被垃圾收集器访问过 且这个对象的所有引用都被扫描过 ,它是安全存活的,

灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没被扫描过 

白色:表示对象尚未被垃圾收集器访问过 , 若分析结束,仍然是白色则表示不可达。

 下面是对应上图的代码实现 

public class ThreeColorRemark {
    public static void main(String[] args) {
        A a = new A();//刚开始的时候 A 引用 B B引用C D
        //开始做并发标记
        D d = a.b.d;
        a.b.d = null; //B 引用 D 取消了 多标
        a.d =d ; // A 新增了 引用 D 漏标

    }

    static class A {
        B b = new B();
        D d = null;
    }

    static class B {
        C c = new C();
        D d = new D();
    }

    static class C {
    }

    static class D {
    }


针对上面的这些情况 可以新引入一些概念帮我们理解:

1>浮动垃圾 (针对多标)

当并发标记过程中,由于方法运行结束而导致部分局部变量(gcroot)被销毁,而这些 gcroot 引用的对象之前已经被扫描过(被标记为非垃圾对象),那么本轮 GC 将不会回收这部分内存。这部分本应该被回收的内存,但却未被回收,被称为 "浮动垃圾"。浮动垃圾不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收时才会被清除。

此外,针对并发标记(还包括并发清理)开始后产生的新对象,通常的做法是将其全部直接视为黑色,本轮不会进行清除。这些对象在之后可能也会变为垃圾,因此也可以看作是浮动垃圾的一部分。

2>读写屏障(针对漏标)

漏标会导致被引用的对象被误删,造成严重的 bug,这必须得到解决。有两种解决方案:

  1. 增量更新(Incremental Update): 增量更新指在黑色对象插入新的指向白色对象的引用关系时,记录这些新插入的引用。并发扫描结束后,以记录过的引用关系中的黑色对象为根,重新扫描一次。简单理解就是,黑色对象一旦新插入了指向白色对象的引用,它就会变回灰色对象。

  2. 原始快照(Snapshot At The Beginning,SATB):

    在CMS垃圾收集中,垃圾收集的主要过程分为两个阶段:标记阶段和清除阶段。标记阶段是并发的,它标记出存活的对象。清除阶段需要停顿,并清理掉已经标记为垃圾的对象。

    然而,由于在并发标记的过程中,对象的引用关系可能会发生变化,导致标记和清除之间的不一致。例如,在标记过程中,一个灰色对象删除了指向白色对象的引用,但是由于并发性,这个变化可能在标记结束后才被感知。这就可能导致白色对象被错误地保留在堆中,造成内存泄漏。

    原始快照(SATB)是CMS垃圾收集器用来解决这个问题的一种方法。在CMS标记的过程中,如果一个灰色对象要删除指向白色对象的引用关系,这个删除操作会被记录下来,形成一个"快照"。然后在标记阶段结束后,这些记录过的引用关系会被用于重新扫描一次(如果有新的引用就不会被删除,如果没有则会被删除),以确保被删除的引用关系被正确地处理。这可以防止因并发引用关系变化而导致的标记-清除不一致性问题。

    总的来说,原始快照是CMS垃圾收集器中的一种技术,用于在并发标记的过程中记录和处理对象引用关系的变化,以确保垃圾收集的正确性。这有助于避免内存泄漏和对象保留问题。

无论是引用关系的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。

写屏障:这里大致介绍下写屏障

给某个对象的成员变量赋值时,其底层代码大概长这样:

 所谓的写屏障,其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念):

 写屏障实现SATB:​​​​​​​当对象B的成员变量的引用发生变化时,比如引用消失(a.b.d = null),我们可以利用写屏障,将B原来成员变量的引用 对象D记录下来:

 写屏障实现增量更新:当对象A的成员变量的引用发生变化时,比如新增引用(a.d = d),我们可以利用写屏障,将A新的成员变量引用对象D 记录下来:

 读屏障:这里大致介绍下读屏障 和 写屏障类似 读之前记录下数据

 oop oop_field_load(oop* field) {
    pre_load_barrier(field); // 读屏障‐读取前操作
    return *field; 
    }
 读屏障是直接针对第一步:D d = a.b.d,当读取成员变量时,一律记录下来:

总结:现代追踪式(可达性分析)的垃圾回收器几乎都借鉴了三色标记的算法思想,尽管实现的方式不尽相同,所以三色标记还是挺需要我们去了解的。

2.记忆集和卡表

在新生代做GCRoots可达性扫描过程中可能会碰到跨代引用的对象,这种如果又去对老年代再去扫描效率太低了。 为此,在新生代可以引入记录集(Remember Set)的数据结构(记录从非收集区到收集区的指针集合),避免把整个老年代加入GCRoots扫描范围。hotspot使用一种叫做“卡表”(cardtable)的方式实现记忆集,也是目前最常用的一种方式。至于如何实现的这里就不赘述了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值