JVM之G1浅析

前置基础概念

Card Table

跟各种垃圾回收器没有太大的关系,主要有用于优化分代模型快速扫描garbage,在可达性分析判断eden区某个对象是否还有引用指向,如果指向eden区对象引用在old区,那么在判断eden区的某个对象是否存活,就需要遍历整个老年代,也就是做一次YGC需要扫描整个old区,就会极大的消耗性能。

为了优化性能,在JVM内部把Young区和Old区切分成了一个一个Card,类似于操作系统把内存切分成很多Page,这些对象存在于一个一个Card里面,在整个程序的演化过程中,如果old区的某个card里有对象指向Eden区的对象,那么就会把这个card标记成DirtyCard,这些card会有一个bitmap来表示,在查找DirtyCard的时候扫描这个位图就可以快速的查找了,这个bitmap就是Card Table

目的就是不需要全量扫描Old区。

CSet(Collecation Set)

闻名而知雅意,收集集合,就是一组可被回收的Region集合,G1会回收垃圾特别多的Region,这些Region的信息集合,就是Collecation Set。

  • 在CSet中的存活的对象会在GC的过程中被移动到另外一个可用的Region。
  • Cset中的分区可以来自Eden区,Survivor区,Old区。
  • CSet占用堆不到1%的大小。

RSet(RememberedSet)

JVM之G1浅析每一个Region里都有一个RememberedSet,RSet里面记录着另外一个Region里的对象指向当前Region对象的引用。

RSet的价值在于使垃圾回收器不需要扫描整个堆就能找到是哪个对象对当前Region里的对象进行了引用

缺点也是有的,那就是每个region都维护一个RSet,比较浪费空间,在ZGC里已经没有RSet了,取而代之的是Color Pointor(颜色指针)。

概念区分:CardTable的作用和RSet作用冲突吗?
这里是我写到这里临时想到的一个问题,
参考:https://blog.****.net/luzhensmart/article/details/106052574

Humongous对象

指的是超过Region的百分之50,或者跨越多个Region的大对象。

G1结构

JVM之G1浅析
垃圾回收器有很多,以前我们常见的垃圾回收器组合有Serial+SerrialOld ,Parallel Scavenge+ParallelOld,ParNew+CMS,这些都是逻辑+物理分代的,就是只有一个Young区和一个Old区,随着需求的增加,heap需要管理的范围也越来越大,导致了每次GC需要扫描也越来越耗时。

而G1回收器只是逻辑分代,并且为了在更大heap范围拥有高速回收的效率,G1采用了分而治之的思想。G1把整个heap切分成了很多的Region,每个Region大小为2的N次幂M,而每个Region都有可能是Eden区,Survivor区,Old区和Humongous区。

复用了之前框架分代的逻辑,但是在物理上不需要连续,这样带来的好处就是有的Region垃圾特别多,有的Region垃圾特别少,G1会优先回收垃圾对象特别多的Region,这样就节省了一大部分扫描的时间。这就是G1(Garbage First)名称的由来。

Young区和Old区如何回收?

新生代依然是满了对新生代进行回收,整个新生代的对象,要么晋升,要么回收,至于新生代也采用Region分区的原因是为了跟老年代策略统一,方便调整代的大小。

在回收老年代的Region,是采用的copying算法,把存活的对象copy到另外一个空闲的Region,这个copy的过程就实现了局部的压缩,copy之后会把原来Region整体的kill掉。

G1的特点

  • 并发收集
  • 压缩空闲空间不会延长GC的STW时间。
  • 更容易预测GC的STW时间
  • 适用于不需要实现很高吞吐量但是需要实现很快响应时间的的场景。

并且新老年代比例,以前是1:2,而G1不需要指定,G1会跟踪每一次停顿(STW), 根据STW动态调整,调整范围是5%-60%

G1的GC分类

G1的GC分为三种:

  • YGC :

年轻代GC,Eden区不足了,就会触发。根据STW动态调整eden区,可能会让Eden区所拥有的Region更多一些或者更少一些。多线程并行执行。

  • MixedGC:

相当于一个CMS,默认触发阈值是45%(可以调整),回收额时候不分Young Old区,是混合回收的。
分为:

1.初始标记(STW):标记根对象。
2.并发标记:从根对象开始找,有引用的都标记出来。
3.最终标记(STW):把哪些漏标的,新产生的标记出来。
4.筛选回收(STW):就是被回收的region中的存活的对象copy到另外一个空闲的Region,这个copy的过程就实现了局部的压缩,copy之后会把原来Region整体的kill掉,碎片也就有没有CMS那么多。

  • FGC:

G1在堆空间分配不下的时候会产生FGC,但是尽量不要让G1产生FGC,解决方案:

1.扩内存
2.提高CPU性能
3.降低MixedGC的阈值。让MixedGC提早触发。

JAVA10之前FGC都是单线程串行的,之后才是并行的。
CMS和G1调优的目标就是不要有FGC的发生。

三色标记算法

JVM之G1浅析

无论是CMS还是G1都采用了三色标记算法,他们的难点都在于并发标记,在对象的标记过程中,对象引用的关系正在发生改变,容易产生漏标的现象。

达到漏标的条件:在并发标记的过程中,B到C的引用被kill掉,并且A和C建立了引用。这个时候由于A已经是被标记为黑色,所以在最终标记的时候A不会被扫描到,B也没有引用指向C,C就会被漏标从而被当成garbage回收掉。

解决方法:把B到C的引用被kill掉,并且A和C建立了引用。这两个条件之一打破就可以了。
CMS使用的是incremental update算法,A建立C引用的时候从新把A标记为灰色,最终标记的时候从新扫描A对象的fields。
G1使用的是SATB(snapshot at the beginning)算法,关注引用的删除,满足B到C的引用消失时,把这个引用推送到GC的堆栈,保证C还能被GC扫描到。

两种算法的区别在意,增量算法关注A指向C的增加,SATB关注B指向C的消失。

RSet和SATB算法的配合

最终标记会扫描GC堆栈中的引用找到对象进行扫描是否有引用指向这个对象(is garbage?)
,这个时候只需要到这个对象的Region中扫描RSet就能快速的判断这个对象到底是不是垃圾了。很大的提升了效率。

戏入人生
希望和大家一起学习进步,如果我朋友理解不到位的地方,请留言指正,谢谢。