3.JVM系列-G1垃圾收集器

目录

一、背景

二、G1垃圾收集器特性

三、G1执行步骤  

四、G1基本参数

四、G1日志解释

六、基本原理

七、G1优化

八、元空间扩容引起MIXGC实践

一、背景

G1垃圾收集器也是以关注延迟为目标、服务器端应用的垃圾收集器,被HotSpot团队寄予取代CMS。本文都是以JDK1.8说明。

二、G1垃圾收集器特性

G1特性说明

特点同CMS获得最低的停顿为目的

利用分而治之的思想来获得可预测的停顿

使用场景1.服务端多核CPU、JVM内存>=6G。

2.应用在运行过程中会产生大量内存碎片、需要经常压缩空间。

3.想要更可控、可预期的GC停顿周期;防止高并发下应用雪崩现象。

4.降低停顿时间

5.FULL GC太频繁

基于算法整体使用标记-整理

局部使用复制

优点1.不会产生内存碎片,有利于程序长时间运行

2.切分堆内存为多个Region每次回收垃圾最多的部分Region,从而降低停顿.

3.适用于大内存,即使很大也仅是对 Region 进行扫描,性能还是很高的。

4.可设置最大停顿时间。

5.每块区域既有可能属于O区、也有可能是Y区,每类区域空间可以是不连续的。

6.Eden, Survivor, Old区不再固定、在内存使用效率上来说更灵活。

7.G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW的时候做。

8.G1会在Young GC中使用、而CMS只能在O区使用。

基本原理把整个 堆划分为若干Region 。每个 Region 大小为2的倍数,范围在 1MB-32MB 之间,所有的 Region 相同在JVM 生命周期内不会被改变。最多2048个region。

第一时间处理垃圾最多的区块。

GC模式Young GC

Mixed GC

Young GC1.年轻代收集进行时,整个年轻代会被回收,所有的应用程序线程会被中断,采用复制算法。

Mixed GC之所以叫混合是因为回收所有的年轻代的Region+部分老年代的Region

大对象(超过region的50%)G1会挑选出1组物理连续的可用 Region, 相加后只要能够确保总大小可以存放这个大对象,就会分配给这个大对象。反之如果没有能够找到符合条件的连续可用Region ,会执行1次Full GC压缩堆。

G1 Full GCFull GC会对整个Java堆进行压缩 G1 Full GC 是单线程的(转换为serial old),会引起较长的停顿时间。

Young GC触发条件当E区不能再分配新的对象

Mixed GC触发条件

1.整个堆占用率大于InitiatingHeapOccupancyPercent

2.元空间超过MetaspaceSize发生扩容

3.调用System.gc且开启ExplicitGCInvokesConcurrent

Full GC触发条件(转换为serial old收集器)

1.没有连续的可用region存放大对象

2.调用System.gc且未开启ExplicitGCInvokesConcurrent

3.没有足够的内存供存活对象或晋升对象使用(报错Allocation Failure)

4.元空间达到MaxMetaspaceSize之后

三、G1执行步骤

1.Mixed GC执行步骤

步骤名称简述是否STW

initial-mark初始标记标记了从GC Root开始直接可达的对象,Mixed GC开始会触发一次完整的ygc,在ygc过程中完整初始标记是

concurrent-root-region-scan根区域扫描这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且收集各个Region的存活对象信息,

concurrent-mark并发标记由前阶段标记过的对象出发,所有可到达的对象都在本阶段中标记

这个阶段可以被年轻代的垃圾收集打断

remark重新标记完成堆内存活对象的标记。使用了一个叫开始前快照snapshot-at-the-beginning (SATB)的算法,这个会比CMS collector使用的算法快。是

Unloading卸载类卸载class是

cleanup清理清除空Region

对存活的对象和完全空的区域进行统计

刷新RSets

concurrent-cleanup重置空的区域,把他们放到free列表否

四、G1基本参数

参数意义

-XX:+UseG1GC启动G1

-XX:G1HeapRegionSize自定义region大小。

-XX:G1HeapRegionSize=2M

-XX:G1HeapWastePercent在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC

-XX:G1MixedGCCountTarget一次global concurrent marking之后,最多执行Mixed GC的次数

-XX:MaxGCPauseMillis设置G1收集过程目标时间,默认值200ms,不是硬性条件

-XX:ConcGCThreads=n并发标记阶段,并行执行的线程数

-XX:InitiatingHeapOccupancyPercent设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous

ResizeTLAB即线程本地分配缓存区线程私有的。

JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。

ResizePLABGC 使用的本地线程分配缓存块采用动态值还是静态值进行设置是由这个选项决定的,

GCTimeRatio这个选项代表 Java 应用线程花费的时间与 GC 线程花费时间的比率。通过这个比率值可以 调节 Java 应用钱程或者 GC 线程的工作时间,保障两者的执行时间

G1ReservePercent预留多少内存,防止晋升失败的情况

四、G1日志解释

1.参数:

-Xmx2000m -Xms2000m -XX:+UseG1GC -XX:+PrintGC -XX:+PrintGCCause -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC  -Xloggc:/tmp/g1test.log

2.

3.样例程序

4.日志

5.完整的一次YGC日志

{Heap before GC invocations=1 (full 0):

 invocations=1第一次发生调用,full 0代表:目前发生full gc(也就是使用serial old gc,整个过程是STW的)次数是0

garbage-first heap   total 2048000K, used 116463K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

使用G1收集器,total 2048000K:整个堆2048000K,used 116463K,包括新生代老年代总共使用 116463K。

region size 1024K, 100 young (102400K), 13 survivors (13312K)

region为1024K,100 young (102400K):整个年轻代有100个region总共占用了102400K,13 survivors (13312K):其中13 个survivors (占用了13312K)

Metaspace       used 3359K, capacity 4500K, committed 4864K, reserved 1056768K

Metaspace:存放类的元数据,如Klass的method 属性等。used 3359K,使用了3359K;总容量capacity 4500K, committed 4864K:JVM向操做系统实际分配的内存4864K,当 committed不会超过MaxMetaspaceSize的值,reserved:JVM向OS申请的虚拟地址空间,保证了其他进程不会被占用。

class space    used 363K, capacity 388K, committed 512K, reserved 1048576K

类的信息存放于此。

2018-11-06T23:02:18.786-0800: 6.107: [GC pause (G1 Evacuation Pause) (young), 0.0849028 secs]

2018-11-06T23:02:18.786-0800:GC发生的时间(通过设置-XX:+PrintGCDateStamps打印),6.107:相对JVM启动的时间, [GC pause (G1 Evacuation Pause) (young), 0.0849028 secs]:GC类型,表示这是evacuation停顿,并且是Young GC,耗时0.0849028 secs。

[Parallel Time: 84.0 ms, GC Workers: 8]

   并行任务花费的STW的时间,从收集开始到最后一个GC线程结束。 GC Workers: 8:并行收集的线程数量。通过 -XX:ParallelGCThreads。当CPU数量小于8时,该值为CPU个数,最大设置成8,对于多于8个的CPU,将默认取CPU个数的5/8。

[GC Worker Start (ms): Min: 6107.4, Avg: 6107.5, Max: 6107.8, Diff: 0.4]

 收集线程启动的时间,使用的是相对JVM启动的时间,Min是最早开始时间,Avg是平均开始时间,Max是最晚开始时间,Diff是Max-Min

[Ext Root Scanning (ms): Min: 0.0, Avg: 0.4, Max: 1.8, Diff: 1.8, Sum: 3.5]

       扫描Roots花费的时间,Sum表示total cpu time

[Update RS (ms): Min: 4.1, Avg: 7.3, Max: 10.0, Diff: 5.9, Sum: 58.6]

       线程花费在更新Remembered Set上的时间

[Processed Buffers: Min: 1, Avg: 1.4, Max: 2, Diff: 1, Sum: 11]

 处理缓存的时间

[Scan RS (ms): Min: 12.2, Avg: 15.1, Max: 18.2, Diff: 6.0, Sum: 120.5]

     扫描每个CSet中Region的RSet,避免了扫描整个老年代

[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.2]

     扫描code root耗时。Code Root是JIT编译后的代码里引用了heap中的对象,引用关系保存在RSet中.

[Object Copy (ms): Min: 59.8, Avg: 60.8, Max: 61.2, Diff: 1.3, Sum: 486.7]

      拷贝活的对象到新region的耗时.

[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.9]

[Termination Attempts: Min: 1, Avg: 1.9, Max: 4, Diff: 3, Sum: 15]

        当GC线程完成任务之后尝试结束到真正结束耗时。因为在结束前他会检查其他线程是否有未完成的任务,帮助完成之后再结束。

[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]

     线程花费在其他工作上的时间

[GC Worker Total (ms): Min: 83.5, Avg: 83.8, Max: 83.9, Diff: 0.4, Sum: 670.7]

每个线程花费的时间总和。

[GC Worker End (ms): Min: 6191.3, Avg: 6191.3, Max: 6191.3, Diff: 0.0]

每个线程的结束时间。

[Code Root Fixup: 0.0 ms]

  用来将code root修正到正确的evacuate之后的对象位置所花费的时间。

[Code Root Purge: 0.0 ms]

 清除code root的耗时,code root中的引用已经失效,不再指向Region中的对象,所以需要被清除。

[Clear CT: 0.2 ms]

 清除card tables 中的dirty card的耗时

[Other: 0.7 ms]

[Choose CSet: 0.0 ms]

[Ref Proc: 0.2 ms]

[Ref Enq: 0.0 ms]

[Redirty Cards: 0.2 ms]

[Humongous Register: 0.0 ms]

[Humongous Reclaim: 0.0 ms]

[Free CSet: 0.1 ms]

    其他事项共耗时0.7ms,其他事项包括选择CSet,处理已用对象,引用入ReferenceQueues,释放CSet中的region。

[Eden: 87.0M(87.0M)->0.0B(87.0M) Survivors: 13.0M->13.0M Heap: 113.7M(2000.0M)->41.5M(2000.0M)]

   Eden从占87M(总87M)清理后占0M(总87M)  Survivors从13清理后13M 整个Heap占113.7M(总2000.0M)-清理后占41.5M(总2000.0M)]

Heap after GC invocations=2 (full 0):

GC后的情况

garbage-first heap   total 2048000K, used 42492K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

region size 1024K, 13 young (13312K), 13 survivors (13312K)

Metaspace       used 3359K, capacity 4500K, committed 4864K, reserved 1056768K

class space    used 363K, capacity 388K, committed 512K, reserved 1048576K

}

[Times: user=0.52 sys=0.01, real=0.09 secs]

整个YGC耗时:0.09 secs,STW=0.09 secs。

6.下面是完整一次MIXGC的日志

mixgc之所以叫mix是因为会发生一次YGC中就开始发生一次老年代的回收,老年代的回收也是从一次YGC开始的。第一个阶段initial-mark在YGC中完整的,约多出平时GC20%的时间。

{Heap before GC invocations=22 (full 0):

garbage-first heap   total 2048000K, used 1320223K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

region size 1024K, 371 young (379904K), 43 survivors (44032K)

Metaspace       used 3362K, capacity 4500K, committed 4864K, reserved 1056768K

class space    used 364K, capacity 388K, committed 512K, reserved 1048576K

2018-11-08T14:46:14.264-0800: 135.707: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.1533342 secs]

解释:initial-mark阶段利用STW停顿期间,跟踪所有可达对象,该阶段和Young GC一起执行

[Parallel Time: 151.8 ms, GC Workers: 8]

[GC Worker Start (ms): Min: 135707.5, Avg: 135707.6, Max: 135707.7, Diff: 0.2]

[Ext Root Scanning (ms): Min: 0.2, Avg: 0.3, Max: 0.4, Diff: 0.2, Sum: 2.2]

[Update RS (ms): Min: 14.9, Avg: 16.5, Max: 18.7, Diff: 3.8, Sum: 132.2]

[Processed Buffers: Min: 4, Avg: 5.1, Max: 6, Diff: 2, Sum: 41]

[Scan RS (ms): Min: 5.9, Avg: 8.2, Max: 9.9, Diff: 3.9, Sum: 65.9]

[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]

[Object Copy (ms): Min: 126.3, Avg: 126.4, Max: 126.5, Diff: 0.2, Sum: 1010.9]

[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.9]

[Termination Attempts: Min: 1, Avg: 87.5, Max: 127, Diff: 126, Sum: 700]

[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.3]

[GC Worker Total (ms): Min: 151.5, Avg: 151.6, Max: 151.6, Diff: 0.2, Sum: 1212.4]

[GC Worker End (ms): Min: 135859.1, Avg: 135859.1, Max: 135859.2, Diff: 0.0]

[Code Root Fixup: 0.0 ms]

[Code Root Purge: 0.0 ms]

[Clear CT: 0.2 ms]

[Other: 1.3 ms]

[Choose CSet: 0.0 ms]

[Ref Proc: 0.3 ms]

[Ref Enq: 0.0 ms]

[Redirty Cards: 0.3 ms]

[Humongous Register: 0.1 ms]

[Humongous Reclaim: 0.0 ms]

[Free CSet: 0.2 ms]

[Eden: 328.0M(328.0M)->0.0B(382.0M) Survivors: 43.0M->42.0M Heap: 1289.3M(2000.0M)->1004.3M(2000.0M)]

Heap after GC invocations=23 (full 0):

garbage-first heap   total 2048000K, used 1028383K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

region size 1024K, 42 young (43008K), 42 survivors (43008K)

Metaspace       used 3362K, capacity 4500K, committed 4864K, reserved 1056768K

class space    used 364K, capacity 388K, committed 512K, reserved 1048576K

}

[Times: user=0.83 sys=0.08, real=0.15 secs]

上面是一次完整的YGC

2018-11-08T14:46:14.417-0800: 135.861: [GC concurrent-root-region-scan-start]

扫描初始化标记阶段Survivor区的root Region并标记出来

2018-11-08T14:46:14.482-0800: 135.925: [GC concurrent-root-region-scan-end, 0.0641499 secs]

2018-11-08T14:46:14.482-0800: 135.925: [GC concurrent-mark-start]

该阶段和应用线程一起执行,并发线程数默认是并行线程数的四分之一,可以通过-XX:ConcGCThreads显示指定。

2018-11-08T14:46:16.525-0800: 137.968: [GC concurrent-mark-end, 2.0435672 secs]

2018-11-08T14:46:16.525-0800: 137.968: [GC remark 2018-11-08T14:46:16.526-0800: 137.969: [Finalize Marking, 0.0002919 secs] 2018-11-08T14:46:16.526-0800: 137.969: [GC ref-proc, 0.0000682 secs] 2018-11-08T14:46:16.526-0800: 137.969: [Unloading, 0.0093801 secs], 0.0134405 secs]

[Times: user=0.02 sys=0.03, real=0.01 secs]

STW时间是=0.01 secs

2018-11-08T14:46:16.539-0800: 137.982: [GC cleanup 1083M->847M(2000M), 0.0026974 secs]

[Times: user=0.01 sys=0.01, real=0.01 secs]

STW时间是=0.01 secs

2018-11-08T14:46:16.542-0800: 137.985: [GC concurrent-cleanup-start]

2018-11-08T14:46:16.542-0800: 137.985: [GC concurrent-cleanup-end, 0.0001850 secs]

总的STW时间是=0.02 secs

六、基本原理

1.堆逻辑图

也分成三组(Eden,Survivor,Old)但是这三种区域没有固定的大小。

2.概念介绍:

为了避免扫整个堆,有点类似数据库全表扫描的实现,采用索引的方式去优化全表扫描,而G1则是通过Rset+card table的方式实现。

CSet:Collection Set,它记录了GC要收集的Region集合。

RSet:全称Remembered Sets, 每个Region维护一个RSet,用来记录外部指向本Region的所有引用,也就是谁引用了我的对象。是一个hashtable,key是对方region的起始地址,value是card table的index。

Card Table: 记录我引用了谁的对象。一个card table覆盖512字节的内存。当该card被引用的时候就会被标记为dirty_card。

RSet与Card Table关系如下图

这样每次只需扫描Rset,会将存货的对象复制到其他region,如果发现RSet为空,则会加入到CSet中在清除阶段会被清除。

SATB:并发标记阶段采用三色标记算法:对象标记了先记为灰色,字段标记完记为黑色。未被标记的是白色,但是由于是并发的可能会产生漏标。一个对象的引用被替换时,可以通过write barrier 将旧引用记录下来。在STW的重新标记阶段重新标记。

七、G1优化

1.选择G1收集器不要设置年轻代大小

显式的使用-Xmn设置年轻代的大小,会干预G1的默认行为。

G1就不会再考虑设定的暂停时间目标,所以本质上说,设定了年轻代大小就相当于禁用了目标暂停时间

G1就无法根据需要增大或者缩小年轻代的小心。既然大小固定了,就无法在大小上做任何改变了。

2.报错concurrent-mark-reset-for-overflow

这表明全局标记堆栈已满,栈溢出。并发标记检测到此溢出,必须重新设置数据结构以再次开始标记,这些过程代价都是很高的。需要尝试增大堆空间。

GC还必须继续对内存进行释放。

拷贝不成功的对象继续留在原来的区域。

对CSets中对区域的RSets任何更新都要重新生成。

3.元空间的设置

元空间大小由MetaspaceSize和MaxMetaspaceSize控制,JVM启动元空间大小并不是MetaspaceSize,

而当占用超过MetaspaceSize时会触发mixGC,。

所以建议MetaspaceSize设置的同MaxMetaspaceSize,防止频繁扩容引起GC。

可以看出committed最大=MaxMetaspaceSize

八、元空间扩容引起MIXGC实践

参数:-XX:MetaspaceSize=1m -XX:MaxMetaspaceSize=1000m

日志:可以看出因为元数据导致发生mixGC。

3.下图可以看出扩容后元数据占用4500,并不是max,以后每次扩容都会发生一次mixGC。

推荐阅读更多精彩内容