GC垃圾回收(3)- 三色标记算法

    技术2022-07-10  244

    1. CMS

    CMS过程在上篇文章 GC垃圾回收(2) 中已经写过。 它分为四个阶段:

    1 initial mark (初始标记) 2 concurrent mark (并发标记) 3 remark (重新标记) 4 concurrent sweep (并发清理)

    其中 并发标记 阶段会有漏标的问题,为解决这个问题,采用了 “三色标记算法”

    2. G1

    G1 GC(Garbage First Garbage Collector)是一种服务端应用使用的垃圾收集器,目标是用在 多核、大内存的机器上,它在大多数情况下可以实现指定的GC暂停时间,同时还能保持较高的吞吐量。它的吞吐量相较PS+PO降低了大概10%~15%,但是大大降低了响应时间,大概200ms的程度

    2.1 G1内存模型

    G1内存模型如下:

    G1相较之前其它的垃圾回收器,对模型进行了改变,不再进行物理分代,采用逻辑分代。

    它不再将连续内存分为Eden区和Old区,而是将内存分为一个个的Region。一块Region(分区)在逻辑上依然分代,分为四种:Eden,Old,Survivor,Humongous(大对象,跨多个连续的Region)。

    它的每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。

    年轻代、幸存区、老年代这些概念还存在,成为了逻辑上的概念,这样方便复用之前分代框架的逻辑。在物理上不需要连续,这带来了额外的好处——有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先回收垃圾最多的分区。

    新生代其实并不适用于这种算法,依然是在新生代满了的时候,对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。

    G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩。每个分区的大小从1M到32M不等,但都是2的幂次方。

    特点:

    并发收集压缩空闲时间不会延长GC的暂停时间更易预测GC的暂停时间适用不需要实现很高吞吐量的场景

    G1与CMS在并发收集时的算法没太大区别,用的是 三色标记 算法。但ZGC和Shenandoah使用的是 颜色指针 Colored Pointers。

    2.2 基本概念

    2.2.1 CSet

    CSet = Collection set一组可被回收的分区的集合。 在ZSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自Eden空间、survivor空间、或者老年代。CSet会占用不到整个堆空间的1%大小。

    2.2.2 Card Table

    主要用于分代模型中帮助垃圾回收。

    为什么需要 card table ? 寻找存活对象并不是一件容易的事。从一个GC root对象寻找,可能被Old区对象引用,这个Old区对象又被Eden区对象引用,那么判断Eden区对象是否存活就需要遍历整个Old区存活对象看是否被Old区对象引用。这样的话每进行一次YGC就要扫描整个Old区。

    所以JVM内部,将内存区域分为一个个的card,对象存在一个个的card里。当老年代某个card中的对象指向了年轻代,就会将这个card标记为 Dirty 。这么多card具体哪个是 Dirty的,用位图BitMap来代表(如0110010010,1表示Dirty),这就是Card Table。

    Card Table :由于做YGC时,需要扫描整个Old区,效率非常低,所以JVM设计了Card Table, 如果一个Old区Card Table中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card。 在结构上,Card Table用BitMap来实现。

    2.2.3 RSet

    RSet = Remembered Set记录了其他Region中的对象到本Region的引用 RSet的价值在于:使得垃圾收集器不需要扫描整个堆,找到谁引用了当前分区中的对象,只需要扫描RSet即可。

    RSet会占用一定的空间,所以ZGC又做了改进,不使用RSet,用颜色指针来标记。

    Rset与赋值的效率:

    由于RSet的存在,那么每次给对象赋引用的时候,就要做一些额外的操作额外的操作,指的是在RSet中做一些额外的记录(在GC中被称为写屏障)这个写屏障 不是 内存屏障

    2.2.4 新老年代比例

    5% ~ 60%(新生代)

    一般不用手动指定也不要手动指定,因为这是G1预测停顿时间的基准

    G1能跟踪STW停顿时间,根据停顿时间动态调整新生代(Y区)比例

    2.2.5 humongous object

    超过单个region的 50% 就是一个大对象,也可跨越多个region。

    2.3 GC何时触发

    YGC Eden区空间不足多线程并行执行 FGC Old空间不足System.gc()

    **注意:**G1也是存在FGC的,并且一定会被触发。当对象分配不下是会产生FGC。

    2.3.1 如果G1产生FGC,应该做什么,如何优化?

    扩内存提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)(主要)降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)

    2.3.2 G1中的MixedGC

    相当于CMSXX:InitiatingHeapOccupacyPercent –默认值45% –当堆内存超过这个值,触发MixedGC

    回收时不分新生代还是老年代什么的,region满了就回收。

    MixedGC过程:

    初始标记 STW并发标记最终标记 STW(重新标记)筛选回收 STW(并行)

    跟CMS非常像,MixedGC最后是筛选回收,多了个筛选步骤。筛选就是找出垃圾最多的region。筛选后将存活对象复制到其他region,再将之前的region清空。

    2.4 并发标记算法

    CMS和G1在并发标记时使用的是同一个算法:三色标记法,使用白灰黑三种颜色标记对象。白色是未标记;灰色自身被标记,引用的对象未标记;黑色自身与引用对象都已标记。

    2.4.1 漏标问题

    在remark过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标。会把白色D对象当作没有新引用指向从而回收掉。

    并发标记过程中,Mutator删除了所有从灰色到白色的引用,会产生漏标。此时白色对象应该被回收

    产生漏标问题的条件有两个: 1.黑色对象指向了白色对象 2.灰色对象指向白色对象的引用消失

    所以要解决漏标问题,打破两个条件之一即可:

    跟踪黑指向白的增加 incremental update:增量更新,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性。CMS采用该方法。记录灰指向白的消失 SATB snapshot at the beginning:关注引用的删除,当灰–>白消失时,要把这个 引用 推到GC的堆栈,保证白还能被GC扫描到。G1采用该方法。

    为什么G1采用SATB而不用incremental update? 因为采用incremental update把黑色重新标记为灰色后,之前扫描过的还要再扫描一遍,效率太低。 G1有RSet与SATB相配合。Card Table里记录了RSet,RSet里记录了其他对象指向自己的引用,这样就不需要再扫描其他区域,只要扫描RSet就可以了。 也就是说 灰色–>白色 引用消失时,如果没有 黑色–>白色,引用会被push到堆栈,下次扫描时拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率比较高。SATB配合RSet浑然天成。

    Processed: 0.030, SQL: 9