G1全称是Garbage First Garbage Collector,使用G1的目的是简化性能优化的复杂性。例如,G1的主要输入参数是初始化和最大Java堆大小、最大GC中断时间。

G1 GC由Young Generation和Old Generation组成。G1将Java堆空间分割成了若干个Region,即年轻代/老年代是一系列Region的集合,这就意味着在分配空间时不需要一个连续的内存区间,即不需要在JVM启动时决定哪些Region属于老年代,哪些属于年轻代。因为随着时间推移,年轻代Region被回收后,又会变为可用状态(后面会说到的Unused Region或Available Region)了。

G1年轻代收集器是并行Stop-the-world收集器,和其他的HotSpot GC一样,当一个年轻代GC发生时,整个年轻代被回收。G1的老年代收集器有所不同,它在老年代不需要整个老年代回收,只有一部分Region被调用。

G1 GC的年轻代由Eden Region和Survivor Region组成。当一个JVM分配Eden Region失败后就触发一个年轻代回收,这意味着Eden区间满了。然后GC开始释放空间,第一个年轻代收集器会移动所有的存储对象从Eden Region到Survivor Region,这就是“Copy to Survivor”过程。

清单1所示是年轻代的回收GC输出日志,在这个日志里面,请见最后一行,年轻代新的大小是224(New Eden)+32(New Survivor)=256MB

清单1 G1回收年轻代

15.910: [GC pause (young), 0.0260410 secs]

[Parallel Time: 18.0 ms, GC Workers: 4]

[GC Worker Start (ms): Min: 15909.7, Avg: 15909.7, Max: 15909.7, Diff: 0.0]

[Ext Root Scanning (ms): Min: 5.6, Avg: 6.1, Max: 6.8, Diff: 1.2, Sum: 24.3]

[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]

[Processed Buffers: Min: 0, Avg: 12.5, Max: 24, Diff: 24, Sum: 50]

[Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3]

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

[Object Copy (ms): Min: 11.1, Avg: 11.8, Max: 12.2, Diff: 1.1, Sum: 47.1]

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

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

[GC Worker Total (ms): Min: 18.0, Avg: 18.0, Max: 18.0, Diff: 0.0, Sum: 72.0]

[GC Worker End (ms): Min: 15927.7, Avg: 15927.7, Max: 15927.7, Diff: 0.0]
[Code Root Fixup: 0.1 ms]

[Code Root Migration: 0.2 ms]

[Clear CT: 0.1 ms]

[Other: 7.6 ms]

[Choose CSet: 0.0 ms]

[Ref Proc: 7.1 ms]

[Ref Enq: 0.1 ms]

[Free CSet: 0.2 ms]
[Eden: 256.0M(256.0M)->0.0B(224.0M) Survivors: 0.0B->32.0M Heap: 256.0M(1024.0M)->35.1M(1024.0M)]

[Times: user=0.06 sys=0.02, real=0.02 secs]

当一个Java堆空间瓶颈点超过后,即堆空间耗尽,这时G1初始化老年代收集器,这个Initial-Mark阶段是一个并行Stop-the-World的,它的大小和老年代以及整个Java堆大小有关。

Initial-Mark阶段和下一个Young

GC一起执行,一旦Initial-Mark完成,一个多线程并行的Marking阶段开始去标记老年代所有存活的对象。当并行Marking阶段完成,一个并行的Stop-the-World的阶段开始去标记所有的对象(由于和Marking阶段并行存在,应用程序多线程程序可能会丢失数据)。Remark节点结束后,G1有老年代所有Region的完整的标记信息,如果这时老年代没有任务存活对象,那么下一个阶段Cleanup阶段就不需要额外的GC工作了。

上面的描述是关于老年代收集器的流程描述,简要说明就是Initial-Mark-> Concurrent Root Region scanning->Concurrent Marking-> Remarking->Cleanup。

在Cleanup阶段,如果G1 GC发现一个可回收的Region,不用等到下一个GC停顿可以直接回收这些Region并且加入到空闲Region的LinkedList链表。

CMS、Parallel、Serial GC都需要通过Full GC去压缩老年代并在这个过程中扫描整个老年代。

因为G1的操作以Region为基础,因此它适用于大Java堆。即便Java堆很大,大量的GC工作可以被限制在小型Region集合里面。G1允许用户指定停顿时间目标,G1通过自适应的堆大小来满足这个目标。

G1 GC深度原理

G1把整个Java堆划分为若干个区间(Regions)。每个Region大小为2的倍数,范围在1MB-32MB之间,可能为1,2,4,8,16,32MB。所有的Region有一样的大小,JVM生命周期内不会改变。例如-Xmx16g

–Xms16g,设置16GB的堆大小,2000个Regions,则每个Region=16GB/2000=8MB。如果堆大小很大,而每个Region的大小很小,则Region数量可能会超过2000个。同样地,很小的堆大小会导致Region数量很少。

Region类型

G1对于Region的几个定义:

Available Region=可用的空闲Region

Eden Region = 年轻代Eden空间

Suivivor Region=年轻代Survivor空间

所有Eden和Survivor的集合=整个年轻代

Humongous Region=大对象Region

Humongous Region

大对象是指占用大小超过一个Region50%空间的对象,这个大小包含了Java对象头。对象头大小在32位和64位HotSpot VM之间有差异,可以使用Java Object Layout工具确定头大小,简称JOL。

当大对象开始进入排队时,G1会调动几个连续的有效Region存放它。第一个Region叫做“大对象开始”Region,其他Regions叫“大对象延续”Regions。如果没有连续的可用Regions,G1会做一个Java
heap的full
gc去压缩对象。大对象区间属于老年代的一部分,它只包含一个对象,这个属性允许G1收集一个大对象区间当并行Marking阶段发现没有对象存活时。当这个条件触发,所有包含大对象的区间都可以立即被回收申明。

对于G1来说,一个潜在的挑战是短生命周期的大对象可能不会被申明直到它们变成没有被引用。JDK8U45申明了一个方法在年轻代回收大对象Region。

前面说过,G1
Region包括Young Region、Old Region、Humougous Region、Free
Region。每个收集器单元跨越一个年轻和老年Regions。一个大对象跨越两个收集器单元,所以大对象Region是一个连续的Region,如图1所示。

图1 Region跨越分布图1

接下来连续的Region叫做Continues Humongous。大对象2跨越三个连续的堆Regions,大对象3跨越了一个Region。

图2 Region跨越分布图2

RSet

基于老年代的收集器采用Heap里不同区域区分/隔离对象,这些不同的区域里面的对象对应了不同年代。这样年代收集器可以集中精力在最近分配的对象上,因为它们会发现一些对象不久会死亡。这些年代在堆里可以被分别收集,这样不用扫描整个Heap,可以节省时间和减小响应时间,并且存活时间长的对象不用来回复制,减少了拷贝和引用更新开销。

为了方便收集器的独立性,许多GC维持了每个年代的RSet。每一个RSet是一个数据结构,它维护并跟踪收集器单元的内部引用,如G1
GC的Region一样,减少了扫描整个Heap堆获取信息的耗时。当G1
GC执行了一个Stop-the-world收集(年轻代或混合代),它可以通过CSet扫描Region的RSets。一旦存活对象被移除,它们的引用立即被更新。

图3 RSet布局图示例

从上面的图3,我们可以看见一个年轻代Region(Region
X)和两个老年代Region(Region Y和Region Z)。Region X有一个从Region
Z来的引用。这个引用被标记在了Region X的RSet里面。我们也观察到Region Z有两个引用,一个来自于Region
X,另一个来自于Region Y。Region Z的RSet需要标记从Region Y过来的引用,但是不需要去记住从Region
X来的引用,因为年轻代是全局被收集的。对于Region Y,最终我们可以看到从Region X来的引用,并没有在Region
Y的RSet里记录引用。

Mixed GC事件

随着很多对象被提升到老年代,以及大对象进入大对象区间,整个Java堆区占有率上升。为了避免Java堆空间溢出,JVM进程需要去初始化一个GC(不仅包含年轻代Regions,也包含增加老年代Region到混合收集器)。在混合GC事件里,所有的年轻代Regions会被收集,同时一部分老年代Region也会被收集。

清单2 G1老年代回收

120.298: [GC pause (mixed), 0.0221810 secs]

[Parallel Time: 17.1 ms, GC Workers: 4]

[GC Worker Start (ms): Min: 120298.2, Avg: 120298.2, Max: 120298.2, Diff: 0.1]

[Ext Root Scanning (ms): Min: 5.6, Avg: 5.8, Max: 5.8, Diff: 0.2, Sum: 23.1]

[Update RS (ms): Min: 0.7, Avg: 0.9, Max: 1.0, Diff: 0.3, Sum: 3.5]

[Processed Buffers: Min: 4, Avg: 10.5, Max: 23, Diff: 19, Sum: 42]

[Scan RS (ms): Min: 1.1, Avg: 1.1, Max: 1.2, Diff: 0.1, Sum: 4.5]

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

[Object Copy (ms): Min: 9.0, Avg: 9.2, Max: 9.5, Diff: 0.4, Sum: 36.8]

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

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

[GC Worker Total (ms): Min: 16.9, Avg: 17.0, Max: 17.0, Diff: 0.1, Sum: 68.0]

[GC Worker End (ms): Min: 120315.2, Avg: 120315.2, Max: 120315.2, Diff: 0.0]
[Code Root Fixup: 0.1 ms]

[Code Root Migration: 0.1 ms]

[Clear CT: 0.1 ms]

[Other: 4.7 ms]

[Choose CSet: 0.1 ms]

[Ref Proc: 4.1 ms]

[Ref Enq: 0.0 ms]

[Free CSet: 0.3 ms]
[Eden: 224.0M(224.0M)->0.0B(224.0M) Survivors: 32.0M->32.0M Heap: 547.5M(1024.0M)->297.3M(1024.0M)]

[Times: user=0.08 sys=0.00, real=0.02 secs]

Full Garbage Collections

G1内部,前面提到的混合GC是非常重要的释放内存机制,它避免了G1出现Region没有可用的情况,否则就会触发Full GC事件。

CMS、Parallel、Serial
GC都需要通过Full GC去压缩老年代并在这个过程中扫描整个老年代。G1的Full GC算法和Serial
GC收集器完全一致。当一个Full GC发生时,整个Java堆执行一个完整的压缩,这样确保了最大的空余内存可用。G1的Full
GC是一个单线程,它可能引起一个长时间的停顿时间,G1的设计目标是减少Full GC,满足应用性能目标。

G1 GC常用参数

我在这里所罗列的参数的默认值都是基于JDK8u45,所以可能后续的JDK版本会有些值不一样,这个读者可以直接通过JDK的官方帮助文档获取最新默认值信息。

-XX:+UseG1GC:启用G1 GC。JDK7和JDK8要求必须显示申请启动G1 GC,JDK可能会设置G1 GC为默认GC选项,也有可能会退到早期的Parallel GC,这个也请关注吧,JDK9预计2017年正式发布;

-XX:G1NewSizePercent:初始年轻代占整个Java Heap的大小,默认值为5%;

-XX:G1MaxNewSizePercent:最大年轻代占整个Java Heap的大小,默认值为60%;

-XX:G1HeapRegionSize:设置每个Region的大小,单位MB,需要为1,2,4,8,16,32其一,默认是堆内存的1/2000。前面我们讲过大对象概念,如果这个值设置比较大,那么大对象就可以进入Region了,同样地,这样做的坏处是直接干预了各年龄代的分配大小;

-XX:ConcGCThreads:与Java应用一起执行的GC线程数量。默认是Java线程的1/4。减少这个参数的数值可能会提升并行回收的效率,即提高系统内部吞吐量(系统是一个整体,CPU资源大家都需要占用),不过如果这个数值过低,也会导致并行回收机制耗时加长;

-XX:+InitiatingHeapOccupancyPercent(简称IHOP):G1内部并行循环启动的设置值,默认为Java

Heap的45%。这个可以理解为老年代空间占用的空间,GC收集后需要低于45%的占用率。这个值主要是为了决定在什么时间启动老年代的并行回收循环,这个循环从初始化并行回收开始,可以避免Full
GC的发生;

-XX:G1HeapWastePercent:G1不会回收的内存大小,默认是堆大小的5%。GC会收集所有的Region,如果值达到5%,就会停下来不再收集了;

-XX:G1MixedGCCountTarget:设置并行循环之后需要有多少个混合GC启动,默认值是8个。老年代Regions的回收时间通常比年轻代的收集时间要长一些,所以如果混合收集器比较多,可以允许G1延长老年代的收集时间;

-XX:+G1PrintRegionLivenessInfo:这个参数需要和-XX:+UnlockDiagnosticVMOptions配合启动,这可以理解,它们本身就是属于VM的调试信息。如果开启了,VM会打印堆内存里每个Region的存活对象信息。这个信息在标记循环结束后可以打印出来;

-XX:G1ReservePercent:这个值是为了保留一些空间用于年代之间的提升,默认值是堆空间的10%。注意这个空间保留后就不会用在年轻代了,大家可以看到GC日志里输出显示,我们大量执行的是年轻代回收,所以如果你的应用里面有比较大的堆内存空间、比较多的大对象存活,那还是减少一点保留空间吧,这样会给年轻代更多的预留空间、GC之间更长的处理时间;

-XX:+G1SummarizeRSetStats:这个也是一个VM的调试信息。如果启用,会在VM推出的时候打印出RSets的详细总结信息。如果启用-XX:G1SummaryRSetStatsPeriod参数,就会阶段性地打印RSets信息;

-XX:+G1TraceConcRefinement:这个也是一个VM的调试信息。如果启用,并行回收阶段的日志就会被详细打印出来;

-XX:+GCTimeRatio:大家知道,GC的有些阶段是需要Stop-the-World,即停止应用线程的,这个参数就是计算花在Java应用线程上和花在GC线程上的时间比率,默认是9。这个参数主要的目的是让用户可以控制花在应用上的时间,G1的计算公式是100/(1+GCTimeRatio),这样如果采用9,则最多10%的时间会花在GC工作上面。Parallel
GC的默认值是99,表示1%的时间被用在GC上面,这是因为Parallel
GC贯穿整个GC,而G1则根据Region来进行划分,不需要全局性扫描Java Heap;

-XX:+UseStringDeduplication:手动开启Java String对象的分割工作,这个是JDK8u20之后新增的参数,主要用于相同String避免重复申请内存,节约Region的使用;

-XX:MaxGCPauseMills:G1停止执行的一个目标值,单位是毫秒,默认是200毫秒,这个值不一定真的会达到。这个参数会通过控制年轻代的大小来实现目标。

结束语

我们知道垃圾回收机制一直是Java着力加强点,G1
GC通过引入许多不同的方式解决了Parallel、Serial、CMS
GC的许多缺点。G1通过将Heap划分为多个Region,可以让G1操作可以在一个Region里面执行而不是整个Java堆或者整个年代(Generation)。本文作者一直的观点是Practice
by self,几乎所有的应用程序都有自身的独特性,所以不能给出千篇一律的GC配置单,需要的是读者自己的反复试验、比对结果、确定方案。

官方文档

http://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html

JDK8新垃圾回收机制--G1垃圾回收机制的更多相关文章

  1. CMS垃圾回收与G1垃圾回收

    CMS垃圾回收与G1垃圾回收的比较请参见:http://colobu.com/2015/04/14/G1-Getting-Started/

  2. jvm默认的并行垃圾回收器和G1垃圾回收器性能对比

    http://www.importnew.com/13827.html 参数如下: JAVA_OPTS="-server -Xms1024m -Xmx1024m -Xss256k -XX:M ...

  3. JVM的垃圾回收机制 总结(垃圾收集、回收算法、垃圾回收器)

     相信和小编一样的程序猿们在日常工作或面试当中经常会遇到JVM的垃圾回收问题,有没有在夜深人静的时候详细捋一捋JVM垃圾回收机制中的知识点呢?没时间捋也没关系,因为小编接下来会给你捋一捋. 一. 技术 ...

  4. JVM垃圾回收机制与内存回收

    暂时转于:https://blog.csdn.net/qq_27035123/article/details/72857739 垃圾回收机制 GC是垃圾回收机制,java中将内存管理交给垃圾回收机制, ...

  5. 关于elasticsearch使用G1垃圾回收替换CMS

    最近ES集群数据节点经常出现jvm占用过高,频繁GC导致ES集群卡死,很长时间才恢复.在网上看到用G1垃圾回收可以改善这一情况,但都是老版本的ES,我们现在使用的版本是5.5.2,所以想问问各位5.5 ...

  6. JVM内存管理机制和垃圾回收机制

    JVM内存管理机制和垃圾回收机制 JVM结构 图片描述: java源码编译成class文件 class文件通过类加载器加载到内存 其中方法区存放的是运行时的常量.静态变量.类信息等,被所有线程共享 堆 ...

  7. python的优化机制与垃圾回收与gc模块

    python属于动态语言,我们可以随意的创建和销毁变量,如果频繁的创建和销毁则会浪费cpu,那么python内部是如何优化的呢? python和其他很多高级语言一样,都自带垃圾回收机制,不用我们去维护 ...

  8. python内存机制与垃圾回收、调优手段

    目录 一.python的内存机制 二.python的垃圾回收 1. 引用计数 1.1 原理: 1.2 优缺点: 1.4 两种情况: 2. 标记清除 2.1 原理: 2.2 优缺点: 3. 分代回收 3 ...

  9. JVM G1垃圾回收算法简要介绍

    JVM G1垃圾回收算法简要介绍 G1的特点 能够像CMS垃圾回收算法一样并发操作应用线程(潜台词:多核) 无需太长时间即可压缩空闲内存空间(潜台词:不会引起太多的GC停顿时间) 尽可能地让GC时长可 ...

随机推荐

  1. 检查你要加入到gradle的第三方library是否是最新版本

    开发者从博客.github readme  或者 官方文档中找到如何在gradle 文件中加入dependency  的时候,往往版本已经比较老旧了,想要找到最新版,介绍一个利器 http://gra ...

  2. SQL Server中的高可用性----复制

    在本系列文章的前两篇对高可用性的意义和单实例下的高可用性做了阐述.但是当随着数据量的增长,以及对RTO和RPO要求的严格,单实例已经无法满足HA/DR方面的要求,因此需要做多实例的高可用性.本文着重对 ...

  3. Oracle 修改任何用户的密码

    1.通过sys用户以sysdba身份登陆: 2.修改用户密码:  alter user 用户名 identified by 密码; 比如修改system的密码为 manager : alter use ...

  4. HCNA多区域OSPF配置

    1.拓扑图 2.各路由器配置角本 ospf 多区域配置 #R5配置 sys sysname AR5 interface s2// ip add 10.0.35.5 255.255.255.0 inte ...

  5. win7 64位 安装java jdk1.8 ,修改配置环境变量

    下载jdk1.8,下载地址:http://www.wmzhe.com/soft-30118.html 安装时有两个程序,都安装在同一个目录下. win7 64位 安装java jdk1.8 ,修改配置 ...

  6. Do the Untwist

      Do the Untwist Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  7. kill 使用当前数据库的所有session

    --在维护中经常需要将某一数据库所有进程都杀掉,手工杀有点太费事.写了一个存储过程 --usage:proc_kill 'PSADBA' create proc proc_kill(@db varch ...

  8. 使用Sleep方法延迟时间

    实现效果: 关键知识:(线程的定义) 实现代码: private void Form1_Load(object sender, EventArgs e) { Thread show = new Thr ...

  9. 2018.10.6 Hibernate配置文件详解-------ORM元数据配置 &&& hibernate主配置文件

    ORM既然是实体与关系数据库的映射,那就需要建立实体和关系数据库之间的基础数据,也可以称为元数据.简单的说就是表示类与表.列与属性(get.set方法)等等之间对应关系的数据. Customer.hb ...

  10. 【转】异步的AsyncHttpClient使用详解

    http://blog.csdn.net/angjunqiang/article/details/55259170 背景 前面的一篇文章[同步的HttpClient使用详解]中,提到了服务端通进行网络 ...