介绍
G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器,分区既可以是年轻代也可以是老年代,同一个代的分区不需要连续。并且每个代分区的数量是可以动态调整的。为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的分区,这也是G1名称的由来。不过这个算法并不适合新生代垃圾收集,因为新生代的垃圾收集算法是复制算法,但是新生代也使用了分区机制主要是因为便于代大小的调整。
G1 GC是设计用来取代CMS的,同CMS相比G1有以下优势:
1、可预测的停顿模型
2、避免了CMS的垃圾碎片
3、超大堆的表现更出色
G1关键概念
Region
G1里面的Region的概念不同于传统的垃圾回收算法中的分区的概念。G1默认把堆内存分为1024个分区,后续垃圾收集的单位都是以Region为单位的。Region是实现G1算法的基础,每个Region的大小相等,通过-XX:G1HeapRegionSize参数可以设置Region的大小。如下图所示:
图中的E代表是Eden区,S代表Survivor,O代表Old区,H代表humongous表示巨型对象(大小大小Region空间一半的对象)。从图中可以看出各个区域逻辑上并不是连续的。并且一个Region在某一个时刻是Eden,在另一个时刻就可能属于老年代。G1在进行垃圾清理的时候就是将一个Region的对象拷贝到另外一个Region中。
SATB
SATB的全称是Snapchat-At-The_Beginning。SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图。在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象。
如何找到在GC的过程中分配的对象呢?每个region记录着两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象就是新分配的,因而被视为隐式marked。通过这种方式我们就找到了在GC过程中新分配的对象,并把这些对象认为是活的对象。
解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢, G1给出的解决办法是通过Write Barrier。Write Barrier就是对引用字段进行赋值做了环切。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化。
RSet
RSet全称是Remember Set,每个Region中都有一个RSet,记录的是其他Region中的对象引用本Region对象的关系(谁引用了我的对象)。G1里面还有另外一种数据结构就Collection Set(CSet),CSet记录的是GC要收集的Region的集合,CSet里的Region可以是任意代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。
停顿预测模型
G1收集器突出表现出来的一点是通过一个停顿预测模型来根据用户配置的停顿时间来选择CSet的大小,从而达到用户期待的应用程序暂停时间。通过-XX:MaxGCPauseMillis参数来设置。这一点有点类似于ParallelScavenge收集器。关于停顿时间的设置并不是越短越好。设置的时间越短意味着每次收集的CSet越小,导致垃圾逐步积累变多,最终不得不退化成Serial GC;停顿时间设置的过长,那么会导致每次都会产生长时间的停顿,影响了程序对外的响应时间。
#G1回收的过程
G1垃圾回收分为两个阶段:
1、全局并发标记阶段(Global Concurrent marking)
2、拷贝存活对象阶段(evacuation)
全局并发标记阶段
全局并发标记阶段是基于SATB的,与CMS有些类似,但是也有不同的地方,主要的几个阶段如下:
初始标记:该阶段会STW。扫描根集合,将所有通过根集合直达的对象压入扫描栈,等待后续的处理。在G1中初始标记阶段是借助Young GC的暂停进行的,不需要额外的暂停。虽然加长了Young GC的暂停时间,但是从总体上来说还是提高的GC的效率。
并发标记:该阶段不需要STW。这个阶段不断的从扫描栈中取出对象进行扫描,将扫描到的对象的字段再压入扫描栈中,依次递归,直到扫描栈为空,也就是说trace了所有GCRoot直达的对象。同时这个阶段还会扫描SATB write barrier所记录下的引用。
最终标记:也叫Remark,这个阶段也是STW的。这个阶段会处理在并发标记阶段write barrier记录下的引用,同时进行弱引用的处理。这个阶段与CMS的最大的区别是CMS在这个阶段会扫描整个根集合,Eden也会作为根集合的一部分被扫描,因此耗时可能会很长。
清理: 该阶段会STW。清点和重置标记状态。这个阶段有点像mark-sweep中的sweep阶段,这个阶段并不会实际上去做垃圾的收集,只是去根据停顿模型来预测出CSet,等待evacuation阶段来回收。
拷贝存活对象阶段
Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。Evacuation阶段从第一阶段选出来的Region中筛选出任意多个Region作为垃圾收集的目标,这些要收集的Region叫CSet,通过RSet实现。
筛选出CSet之后,G1将并行的将这些Region里的存活对象拷贝到其他Region中,这点类似于ParalledScavenge的拷贝过程,整个过程是完全暂停的。关于停顿时间的控制,就是通过选择CSet的数量来达到控制时间长短的目标。
G1的收集模式:
YoungGC:收集年轻代里的Region
MixGC:年轻代的所有Region+全局并发标记阶段选出的收益高的Region
无论是YoungGC还是MixGC都只是并发拷贝的阶段。
分代G1模式下选择CSet有两种子模式,分别对应YoungGC和mixedGC:
YoungGC:CSet就是所有年轻代里面的Region
MixedGC:CSet是所有年轻代里的Region加上在全局并发标记阶段标记出来的收益高的Region
G1的运行过程是这样的,会在Young GC和Mix GC之间不断的切换运行,同时定期的做全局并发标记,在实在赶不上回收速度的情况下使用Full GC(Serial GC)。初始标记是搭在YoungGC上执行的,在进行全局并发标记的时候不会做Mix GC,在做Mix GC的时候也不会启动初始标记阶段。当MixGC赶不上对象产生的速度的时候就退化成Full GC,这一点是需要重点调优的地方。
G1最佳实践
在使用G1垃圾收集器的时候遵循以下实践可以少走不少弯路:
不断调优暂停时间指标
通过XX:MaxGCPauseMillis=x可以设置启动应用程序暂停的时间,G1在运行的时候会根据这个参数选择CSet来满足响应时间的设置。一般情况下这个值设置到100ms或者200ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。
不要设置新生代和老年代的大小
G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的大小。
关注Evacuation Failure
Evacuation Failure类似于CMS里面的晋升失败,堆空间的垃圾太多导致无法完成Region之间的拷贝,于是不得不退化成Full GC来做一次全局范围内的垃圾收集。
G1常用参数
-XX:+UseG1GC | 使用 G1 垃圾收集器 |
-XX:MaxGCPauseMillis=200 | 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到) |
-XX:InitiatingHeapOccupancyPercent=45 | 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示”一直执行GC循环”. 默认值为 45. |
-XX:NewRatio=n | 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2. |
-XX:SurvivorRatio=n | eden/survivor 空间大小的比例(Ratio). 默认值为 8. |
-XX:MaxTenuringThreshold=n | 提升年老代的最大临界值(tenuring threshold). 默认值为 15. |
-XX:ParallelGCThreads=n | 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同. |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同. |
-XX:G1ReservePercent=n | 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10. |
-XX:G1HeapRegionSize=n | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb. |
G1日志分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//新生代GC 2018-05-03T10:21:43.209-0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0035356 secs] //初始标记,耗时0.0035秒 [Parallel Time: 2.4 ms, GC Workers: 8] //并行8个线程,耗时2.4ms [GC Worker Start (ms): Min: 813.1, Avg: 813.7, Max: 813.9, Diff: 0.7] [Ext Root Scanning (ms): Min: 0.0, Avg: 1.1, Max: 1.5, Diff: 1.5, Sum: 9.1] //每个扫描root的线程耗时 [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //更新RS的耗时,G1中每块区域都有一个RS与之对应,RS记录了该区域被其他区域引用的对象。回收时,就把RS作为根集的一部分,从而加快回收 [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0] //Processed Buffers就是记录引用变化的缓存空间 [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //扫描RS [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] //根扫描耗时 [Object Copy (ms): Min: 0.0, Avg: 0.5, Max: 1.3, Diff: 1.3, Sum: 3.6] //对象拷贝 [Termination (ms): Min: 0.0, Avg: 0.2, Max: 0.2, Diff: 0.2, Sum: 1.2] [Termination Attempts: Min: 1, Avg: 1.8, Max: 4, Diff: 3, Sum: 14] [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1] [GC Worker Total (ms): Min: 1.6, Avg: 1.8, Max: 2.3, Diff: 0.8, Sum: 14.1] //GC线程耗时 [GC Worker End (ms): Min: 815.4, Avg: 815.4, Max: 815.4, Diff: 0.0] [Code Root Fixup: 0.0 ms] [Code Root Purge: 0.0 ms] [Clear CT: 0.1 ms] //清空CardTable耗时,RS是依赖CardTable记录区域存活对象的 [Other: 1.1 ms] [Choose CSet: 0.0 ms] //选取CSet [Ref Proc: 0.9 ms] //弱引用、软引用的处理耗时 [Ref Enq: 0.0 ms] //弱引用、软引用的入队耗时 [Redirty Cards: 0.1 ms] [Humongous Register: 0.0 ms] [Humongous Reclaim: 0.0 ms] [Free CSet: 0.0 ms] //释放被回收区域的耗时(包含他们的RS) [Eden: 5120.0K(24.0M)->0.0B(12.0M) Survivors: 0.0B->2048.0K Heap: 16.0M(50.0M)->12.4M(50.0M)] [Times: user=0.01 sys=0.00, real=0.01 secs] //根区域扫描 2018-05-03T10:21:43.213-0800: [GC concurrent-root-region-scan-start] 2018-05-03T10:21:43.214-0800: [GC concurrent-root-region-scan-end, 0.0012422 secs] // 并发标记 2018-05-03T10:21:43.214-0800: [GC concurrent-mark-start] 2018-05-03T10:21:43.214-0800: [GC concurrent-mark-end, 0.0004063 secs] //重新标记又叫最终标记 2018-05-03T10:21:43.214-0800: [GC remark 2018-05-03T10:21:43.215-0800: [Finalize Marking, 0.0003736 secs] 2018-05-03T10:21:43.215-0800: [GC ref-proc, 0.0000533 secs] 2018-05-03T10:21:43.215-0800: [Unloading, 0.0007439 secs], 0.0013442 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] //独占清理 2018-05-03T10:21:43.216-0800: [GC cleanup 13M->13M(50M), 0.0004002 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] |
这是一段完整的GC日志。从整体上看,并发标记周期和混合回收的前后都有可能穿插着新生代GC。其中并发标记周期主要是回收老年代空间,当然也包含了一次新生代GC。
----------------------------------------------------------------
欢迎关注我的微信公众号:yunxi-talk,分享Java干货,进阶Java程序员必备。
相关推荐
《深入理解JVM & G1 GC》一书深入剖析了Java虚拟机(JVM)的工作原理,特别是针对垃圾收集器(GC)中的G1(Garbage-First)算法进行了详尽的探讨。JVM是Java程序运行的基础,它负责解析、编译、执行Java代码,并管理...
《深入理解JVM & G1 GC》这篇文章和相关压缩包文件主要聚焦于Java虚拟机(JVM)的内存管理,特别是垃圾收集器(GC)的优化,特别是G1(Garbage-First)垃圾收集器的深度解析。下面将详细阐述JVM、GC的基本概念,...
GC)日志的开源工具,源自Tagtraum Industries的项目,并且在原作者停止开发后由其他人接手并继续更新,以支持Sun/Oracle Java 1.6及更高版本的垃圾收集器日志,包括G1收集器。这个工具对于Java开发者来说极其重要...
2. **GC策略选择**:根据应用特性选择合适的GC策略,例如,对响应时间敏感的应用可以选择低延迟的G1或ZGC。 3. **监控与诊断**:使用JVisualVM、JConsole、JFR等工具进行实时监控,分析GC日志,找出性能瓶颈。 4. ...
这篇博客文章"分析gclog的程序"可能讲述了如何解析和分析这些日志,以便更好地理解Java应用程序的内存行为。 首先,我们需要了解`gclog`是什么。在Java 7及以后的版本中,JVM引入了增强的日志格式,包括`gclog`,它...
1. **日志解析**:GCViewer能够读取JVM生成的标准GC日志格式,并将其转化为易于理解的图表,这些日志通常通过`-Xlog:gc*`或`-XX:+PrintGCDetails`等JVM参数启用。 2. **实时监控**:用户可以实时观察到应用程序的...
其次,GCViewer支持多种GC算法的分析,包括串行GC、并行GC、CMS(Concurrent Mark Sweep)、G1(Garbage-First)以及ZGC(Z Garbage Collector)等。不同的GC算法有着不同的优化目标和工作模式,通过GCViewer,...
该 Java 库为 Oracle JDK 1.7 和 1.8 生成的 GC 日志提供解析器。 它包括一个命令行实用程序,用于从日志生成 CSV 文件。 您可以将它们加载到您最喜欢的工具中进行分析。 请参阅以下鼓舞人心的文章作为示例 -。 要求...
2. **垃圾收集器类型**:不同的JVM配置会有不同的垃圾收集器组合,如Parallel GC、CMS、G1等。GCViewer能揭示每种收集器的效率和特性。 3. **暂停时间(Pause Time)**:GC暂停是影响应用响应速度的重要因素。通过...
1. **日志解析**:GCViewer能够解析各种格式的GC日志,包括Sun/Oracle JVM、IBM JVM以及OpenJ9等产生的日志,支持G1、Parallel、CMS、Serial等多种GC算法的日志。 2. **实时可视化**:工具将日志中的数据转化为实时...
常见的GC算法有Serial、Parallel、CMS(Concurrent Mark Sweep)和G1(Garbage-First)等。 1. Serial GC:适用于单线程环境,采用复制算法,简单但可能导致应用暂停时间较长。 2. Parallel GC:多线程版本的Serial...
同时,分析结果也能帮助我们选择合适的垃圾收集器,如CMS、G1、ZGC或Shenandoah,以及调整相关的GC参数,以达到最优的性能表现。 在实际使用中,我们需要先在JVM启动时开启GC日志记录,例如在Java启动参数中添加`-...
这个文件很可能是上述描述的“软设历年试题合集”的实际内容,其中可能包含了多个部分,如历年的考试真题、解析、复习指南、GC在软件设计中的应用实例等。 在“gc.chm”这个合集中,读者可以期待学习到以下知识点:...
3. **GC策略分析**:针对不同的GC算法,如Serial、ParNew、Parallel Scavenge、CMS、G1等,GChisto能解析出与这些算法相关的特定指标,帮助用户了解哪种策略更适合其应用。 4. **暂停时间分布**:对于那些对应用...
《深入解析JVM Full GC与调优》 在Java开发中,JVM(Java Virtual Machine)扮演着至关重要的角色,它是Java程序运行的基础。而Full GC作为JVM内存管理的一部分,是每个Java开发者都需要理解的重要概念。本篇将详细...
《JVM、GC详解及调优》是一份深入解析Java虚拟机(JVM)和垃圾收集(Garbage Collection,简称GC)的详细资料。本文将根据提供的信息,深入阐述JVM的工作原理,GC的机制以及如何进行JVM的性能调优。 首先,JVM是...
在《浅谈JVM内存管理》的PPT中,可能包含了对上述概念的详细讲解,包括JVM内存模型的解析、GC算法的工作原理、如何配置和调整GC参数,以及通过实例分析GC调优的具体步骤。通过学习这个PPT,开发者可以深入理解JVM...
在Java世界中,JVM(Java虚拟机)是运行所有Java应用程序的核心,它负责解析字节码、管理内存以及执行线程。本教程——“深入JVM内核—原理、诊断与优化视频教程”着重讲解了JVM的内部机制,特别是关于垃圾收集...
3. **GC策略与调优**:Java提供了多种GC算法,如Serial、Parallel、Concurrent Mark Sweep (CMS) 和 Garbage First (G1)。每种策略都有其适用场景和优缺点。例如,G1适合大内存、低延迟的应用,而CMS则在内存较小、...
2. **垃圾收集器类型**:Java中有多种GC实现,如Serial GC、Parallel GC、CMS(Concurrent Mark Sweep)、G1(Garbage First)和ZGC(Z Garbage Collector)。每种都有其特定的设计目标和适用场景,比如Serial适合轻...