JVM G1源码分析和调优书 阅读笔记 五 第6章 混合回收

6.1 并发标记算法

      用Prev指针指向上一次并发处理后的地址,用Next指向并发标记开始之前内存已经分配成功的地址。

并发标记结束后,只需要把Prev指针设置为Next指针,即可开始新一轮的标记处理。

     并发标记引入两个位图 PrevBitMap 和 NextBitMap ,来指示哪块内存已经使用,哪块未使用

    PrevBitMap 记录prev指针之前内存的标记状况,NextBitMap标识整个内存到next指针之前的标记状态

   发生标记失败,就会丢失上一次对Prev指针之前所有内存的标记状况

并发标记结束后,NextBitMap标记了分区对象存活的情况,黑色区域表示堆分区中对应的对象还活着

扫描二维码关注公众号,回复: 8866074 查看本文章

6.2.1  三色标记法

         白色: 表示还没有被收集器标记的对象

         灰色 : 表示自身已经被标记到,但其拥有的field字段引用到的其他对象还没有被处理

         黑色: 表示自身已经被标记到,且对象本身拥有的field引用到的对象也已经被标记

mutator线程:在垃圾收集器的术语中被称为Java应用线程。

对象在并发标记阶段,会被漏标的充分必要条件

   

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

对象1的field更新了,指向了对象3。那么就需要把对象1重新从黑色,更新标记为灰色,再重新处理一遍field

6.2.3 再谈写屏障

        RSet中写屏障的主要目的是为了标记引用关系,STAB中写屏障主要是为了保证并行标记的正确性,主要记录的是目的对象修改之前的对象。

把要写的目标对象放入到STAB的队列中

    STAB全称Snapshot-At-The-Beginning,由字面理解,是GC开始时活着的对象的一个快照。它是通过Root Tracing得到的,作用是维持并发GC的正确性

    SATB利用pre-write barrier,将所有即将被修改引用关系的白对象旧引用记录下来,最后以这些旧引用为根重新扫描一遍,以解决白对象引用被修改产生的漏标问题。

   赋值处理,直接写对象地址到目标地址

   赋值后处理主要是,把源对象放入到dirty card队列

6.3 G1混合回收的步骤

     1.初始标记子阶段 : 负责标记所有直接可达的根对象(栈对象,全局对象,JNI对象),初始标记需要将Mutator线程暂停,需要一个STW的时间段。混合收集的初始标记,借用了新生代收集的结果,即新生代垃圾回收后的新生代Survivor分区作为根,所有混合收集一定发生在新生代回收之后,且不需要再进行一次初始标记

   2.并发标记子阶段 :根据新生代的Survivor分区以及老生代的RSet开始并发标记,在YGC后,达到InitiatiingHeapOccupancyPercent阈值后,才会触发并发标记,默认值为45,即已经分配的内存加上即将分配的内存超过内存总容量的45%就可以开始并发标记。标记线程和Mutator线程并发运行

 3.再标记子阶段 : G1需要一个暂停的时间,找出所有未被访问的存活对象,同时完成存活内存数据计算。

4. 清理子阶段:也是STW的

   

6.4.1 并发标记线程启动的时机 

          如果YGC的最后阶段判定,如果可以启动并发标记,就调用doConcurrentMark发送通知

          老生代使用的内存加上本次即将分配的内存,达到总内存的45%,就启动并发标记任务

6.4.2 根扫描子阶段

        并发标记线程启动之后,需要开始执行扫描处理

YGC结束阶段,会把Survivor区当做CM扫描的根,分区处理的时候需要对整个分区完全处理,需要遍历整个有效分区

6.4.3 并发标记子阶段

        处理SATB缓存;根据已经标记的分区nextMarkBitmap的对象进行处理,处理的方式是针对已标记对象的每一个field进行递归并发标记。每个并发标记线程从全局指针开始抢占分区,其实使用CAS指令进行串行处理,所有线程停止的条件就是所有分区处理完毕。在并发标记子阶段如果待标记对象过多,可能导致标记栈溢出,会再次循环处理根标记和并发标记子阶段。

6.4.4 再标记子阶段  

        对SATB处理,需要STW,并且是并行处理。结束之后,整个分区都已经完成了标记

6.4.5 清理子阶段

        也需要STW,清理阶段的任务包括  分区信息计数,额外处理,RSet清理

       1.对分区进行计数

          并行工作,每个不同的线程处理不同的分区,最后汇总到卡表中

          如果有空的老分区或者大对象分区,则释放。如果RSet处理进行了扩展,则回收空间;把Old分区加入CSet Chooser

          把垃圾比较多的老生代分区加入到CSet Chooser

       2. 额外信息处理

            对整个堆分区中完全空白的老生代和大对象分区进行释放,对于其他的分区处理RSet,RSet的粒度发生了变化,那么变化前的数据结构可以被删除

      3.RSet清理  

         通过G1ParScrubRemSetTask并行清理Rset

      4. 老生代回收集处理

          根据老生代分区的垃圾空闲情况,只有达到收集的阈值,才可能被加入到CSet  Chooser

6.4.6 启动混合收集

  真正的回收时候,会根据预测时间来选择收集的分区

CSet记录的是GC要收集的Region的集合,CSet里的Region可以是任意代的

在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。

   支持动态调整的分区设置:

       1.预测时间超过目标停止时间,到达最小收集数则停止添加CSet;但是如果老生代收集数还没有到达最小收集数,继续添加分区到CSet,同时记录多少个分区

       2.不支持动态调整的分区设置,只要到达最小收集数则停止添加CSet,所以不要指定固定新生代大小

              

    

  

发布了331 篇原创文章 · 获赞 1 · 访问量 3502

猜你喜欢

转载自blog.csdn.net/kuaipao19950507/article/details/103828117
今日推荐