所有的垃圾回收器的目标都是朝着缩小STW的目标而后退,G1(Garbage First)回收器的呈现颠覆了之前版本CMS、Parallel等垃圾回收器的分代收集形式,从2004年Sun公布第一篇对于G1的论文后,直到2012年JDK7公布更新版本,花了将近10年的工夫G1才达到商用的水平,而到JDK9公布之后,G1成为了默认的垃圾回收器,CMS也变相地相当于被淘汰了。

G1构造

G1摈弃了之前的分代收集的形式,面向整个堆内存进行回收,把内存划分为多个大小相等的独立区域Region。

一共有4种Region:

  1. 自在分区Free Region
  2. 年老代分区Young Region,年老代还是会存在Eden和Survivor的辨别
  3. 老年代分区Old Region
  4. 大对象分区Humongous Region

每个Region的大小通过-XX:G1HeapRegionSize来设置,大小为1~32MB,默认最多能够有2048个Region,那么依照默认值计算G1能治理的最大内存就是32MB*2048=64G。

对于大对象的存储,存在Humongous概念,对G1来说,超过一个Region一半大小的对象都被认为大对象,将会被放入Humongous Region,而对于超过整个Region的大对象,则用几个间断的Humongous来存储(如下图H区域)。

G1劣势

下面咱们也提到,垃圾回收器的最终目标都是为了缩小STW造成的进展,比方之前老的垃圾回收器CMS这种带来的进展工夫是不可预估的。

而G1最大的劣势就在于可预测的进展工夫模型,咱们能够本人通过参数-XX:MaxGCPauseMillis来设置容许的进展工夫(默认200ms),G1会收集每个Region的回收之后的空间大小、回收须要的工夫,依据评估失去的价值,在后盾保护一个优先级列表,而后基于咱们设置的进展工夫优先回收价值收益最大的Region。

那么,这个可预测的进展工夫模型怎么计算和建设的?次要是基于衰减平均值的实践根底,衰减均匀是一种数学方法,用来计算一个数列的平均值,给近期的数据更高的权重,强调近期数据对后果的影响,代码如下:

hotspot/src/share/vm/gc_implementation/g1/g1CollectorPolicy.hppdouble get_new_prediction(TruncatedSeq* seq) {  return MAX2(seq->davg() + sigma() * seq->dsd(),              seq->davg() * confidence_factor(seq->num()));}

davg示意衰减值

sigma示意一个系数,代表信贷度,默认值为0.5

dsd示意衰减标准偏差

confidence_factor示意可信度系数,用于当样本数据有余(小于5个)时取一个大于1的值,样本数据越少该值越大。

基于这个模型,G1心愿依据用户设置的进展工夫(只是冀望工夫,尽量致力在这个范畴内实现GC)来抉择须要对哪些Region进行回收,能回收多大空间。

比方过来10次回收10G内存破费1s,如果预设的进展工夫是200ms,那么就最多能够回收2G的内存空间。

空间调配&扩大

既然G1还是存在新生代和老年代的概念,那么新生代和老年代的空间是怎么划分的呢?

在G1中,新增了两个参数G1MaxNewSizePercentG1NewSizePercent,用来管制新生代的大小,默认的状况下G1NewSizePercent为5,也就是占整个堆空间的5%,G1MaxNewSizePercent默认为60,也就是堆空间的60%。

假如当初咱们的堆空间大小是4G,依照默认最大2048个Region计算,每个Region的大小就是2M。

初始新生代的大小那么就是200M,大概100个Region格子,动静扩大最大就是60%*4G=2.4G大小。

不过显然,事件不是这么简略,实际上初始化新生代的空间大小逻辑还是挺简单的。

首先,咱们通过原有参数-Xms设置初始堆的大小,-Xmx设置最大堆的大小还是失效的,能够设置堆的大小。

  1. 能够通过原有参数-Xmn或者新的参数G1NewSizePercentG1MaxNewSizePercent来设置年老代的大小,如果设置了-Xmn相当于设置G1NewSizePercent=G1MaxNewSizePercent
  2. 接着看是不是设置了-XX:NewRatio(示意年老代与老年代比值,默认值为2,代表年老代老年代大小为1:2),如果1都设置了,那么疏忽NewRatio,反之则代表G1NewSizePercent=G1MaxNewSizePercent,并且调配规定还是依照NewRatio的规定。
  3. 如果只是设置了G1NewSizePercentG1MaxNewSizePercent中的一个,那么就依照这两个参数的默认值5%和60%来设置。
  4. 如果设置了-XX:SurvivorRatio,默认为8,那么Eden和Survivor还是依照这个比例来调配

依照这个规定,咱们新生代和老年代的空间调配根本就实现,如果说新生代走默认的规定,每次动静扩大空间大小怎么办?

有一个参数叫做-XX:GCTimeRatio示意GC工夫与利用消耗工夫比,默认为9,就是说GC工夫和利用工夫占比超过10%才进行扩大,扩大比例为20%,最小不能小于1M。

回收过程

G1的回收过程分为以下四个步骤:

  1. 初始标记:标记GC ROOT能关联到的对象,须要STW
  2. 并发标记:从GCRoots的间接关联对象开始遍历整个对象图的过程,扫描实现后还会重新处理并发标记过程中产生变动的对象
  3. 最终标记:短暂暂停用户线程,再解决一次,须要STW
  4. 筛选回收:更新Region的统计数据,对每个Region的回收价值和老本排序,依据用户设置的进展工夫制订回收打算。再把须要回收的Region中存活对象复制到空的Region,同时清理旧的Region。须要STW。

总的来说这是一个偏差记忆的回收过程,晓得就行了。

绝对于之前咱们存在分代概念的GC来说,G1其实也是相似的过程,总体能够分为这两种:

  1. 年老代GC,年老代Region在超过咱们默认设置的最大大小之后就会触发GC,还是用的咱们相熟的复制算法,Eden和Survivor来回倒腾,这里不再赘述。
  2. Mixed GC混合回收,混合回收相似于之前咱们的Full GC概念,既会回收年老代的Region,也会回收老年代的Region,还有咱们新的Humongous大对象区域。触发规定依据参数-XX:InitiatingHeapOccupancyPercent(默认45%)值,也就是说老年代Region达到整个堆内存的45%时触发Mixed GC。

其余问题

下面应该把基本概念都解释完了。

比方什么是G1?G1有什么特点?他的长处是什么?划分Region后怎么调配空间?怎么进行垃圾回收?什么时候进行YGC?什么时候进行FGC?牢靠的进展工夫模型建设形式?

除此之外,其实还有一些较为简单的问题,比方之前咱们说分代收集有跨代援用的问题,划分Region之后应该也有对不对,那怎么解决的?

还有之前咱们说并发收集阶段怎么解决用户线程和收集线程互不烦扰的?

这些更深一点的问题其实在当初曾经卷到须要问三色标记了吗?曾经说到了很多了,上面咱们再具体点阐明下在G1中的一些不同点。

记忆集

在这篇文章中咱们提到过一次对于Remembered Set的概念,为了防止GC时扫描整个堆内存,用来标记哪些区域存在跨代援用,对于G1来说也一样,只不过G1的记忆集会更简单一点。

每个Region中都存在一个Hash Table构造的记忆集,Key为其余Region的起始地址,Value是其余Card Table卡表的索引汇合。

原来咱们的卡表指向的是卡页的内存地址段,代表我援用了谁,当初的记忆集则是代表着谁援用了我,因而收集的过程会更简单一点,并且须要额定的10%~20%的堆内存空间来维持。

保护记忆集的形式也和卡表相似,通过写屏障来实现。

原始快照SATB

在三色标记中咱们也提到过,并发标记用户线程和收集线程一起工作会产生问题,解决方案CMS应用的是增量更新,G1则是用原始快照。

总结

写这些货色比拟吃力,因为总在想在了解的根底上怎么写的更通俗易懂,然而发现如同并不容易,因为本人也都是看完没过多久就遗记了,所以记录下来,能看懂就行了,切实不行就去看书。

周老师的深刻Java虚拟机写的比较简单,很多货色要去搜材料和书联合看能力看明确,另外一本书写的也不是很好,作者感觉只是堆砌知识点,看起来很吃力,美团写的那篇文章也是一大堆名词,不晓得的人看的几乎蛋疼。

我应该,比他们写的更艰深一点就好了?

参考:

彭成寒《JVM G1源码剖析和调优》

周志明《深刻了解Java虚拟机第三版》

美团:Java Hotspot G1 GC的一些关键技术