开发过程中,经常需要对GC的垃圾收集器参数不断的进行动态调整,从而更充分的压榨机器性能,提升应用效率。本文将从常见的Parallel/G1垃圾收集器的GC日志着手,分析GC日志的具体含义,以及示范如何根据GC日志调整参数。

1. 准备工作

(1) VM Options

应用程序需要设置如下参数,以便将GC信息数值至gc.log文件中,供后续分析。

-XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./logs -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./logs/gc.log

(2) GC统计工具

为了更好的统计GC日志信息,我们将借助于GCViewer桌面软件(开源,地址为:https://github.com/chewiebug/GCViewer),下载之后执行maven clean package -Dmaven.test.skip=true即可完成程序的编译,双击target文件夹下的gcviewer-1.36-SNAPSHOT.jar(具体名称由下载的GCViewer版本确定)即可执行程序。

原GCViewer于2008年后停止维护,上文提供的仓库为chewiebug维护的Fork版本。

(3) 机器信息

本机参数:

jdk版本:Java HotSpot(TM) 64-Bit Server VM (25.172-b11) for windows-amd64 JRE (1.8.0_172-b11).

(4) 测试程序

自行编写的Flink应用(主要优点是:吃内存,加上写文章时正好在家里学习Flink...)。

2. Parallel收集器

Java HotSpot(TM) 64-Bit Server VM (25.172-b11) for windows-amd64 JRE (1.8.0_172-b11), built on Mar 28 2018 21:21:52 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16768628k(8408808k free), swap 33535356k(23695248k free)
CommandLine flags: -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$./logs -XX:InitialHeapSize=268298048 -XX:MaxHeapSize=4292768768 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
2019-10-24T15:33:39.513+0800: 2.164: [GC (Allocation Failure) [PSYoungGen: 65536K->7875K(76288K)] 65536K->7891K(251392K), 0.0275982 secs] [Times: user=0.17 sys=0.00, real=0.03 secs]
2019-10-24T15:33:41.320+0800: 3.970: [GC (Allocation Failure) [PSYoungGen: 73411K->10738K(76288K)] 73427K->12337K(251392K), 0.0147806 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
2019-10-24T15:33:41.772+0800: 4.422: [GC (Metadata GC Threshold) [PSYoungGen: 22142K->6329K(76288K)] 23741K->7935K(251392K), 0.0076904 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2019-10-24T15:33:41.780+0800: 4.430: [Full GC (Metadata GC Threshold) [PSYoungGen: 6329K->0K(76288K)] [ParOldGen: 1606K->6175K(100352K)] 7935K->6175K(176640K), [Metaspace: 20679K->20679K(1067008K)], 0.0334417 secs] [Times: user=0.23 sys=0.00, real=0.03 secs]
2019-10-24T15:33:42.076+0800: 4.726: [GC (Allocation Failure) [PSYoungGen: 65536K->10738K(132608K)] 71711K->64480K(232960K), 0.0512660 secs] [Times: user=0.06 sys=0.42, real=0.05 secs]
2019-10-24T15:33:42.164+0800: 4.815: [GC (Allocation Failure) [PSYoungGen: 132594K->10738K(141824K)] 186336K->184366K(316416K), 0.1241149 secs] [Times: user=0.03 sys=0.94, real=0.12 secs]
2019-10-24T15:33:42.288+0800: 4.939: [Full GC (Ergonomics) [PSYoungGen: 10738K->8672K(141824K)] [ParOldGen: 173628K->174573K(399872K)] 184366K->183245K(541696K), [Metaspace: 22192K->22192K(1069056K)], 0.0938949 secs] [Times: user=0.39 sys=0.13, real=0.09 secs]
2019-10-24T15:33:42.415+0800: 5.066: [GC (Allocation Failure) [PSYoungGen: 139744K->120826K(320000K)] 314317K->312422K(719872K), 0.1433171 secs] [Times: user=0.00 sys=1.08, real=0.14 secs]
2019-10-24T15:33:43.607+0800: 6.257: [GC (GCLocker Initiated GC) [PSYoungGen: 319994K->161768K(360960K)] 511592K->392382K(760832K), 0.2255222 secs] [Times: user=0.36 sys=1.39, real=0.22 secs]
2019-10-24T15:33:45.921+0800: 8.572: [GC (Metadata GC Threshold) [PSYoungGen: 324597K->172709K(544768K)] 555211K->403387K(944640K), 0.1776332 secs] [Times: user=0.03 sys=1.16, real=0.18 secs]
2019-10-24T15:33:46.099+0800: 8.749: [Full GC (Metadata GC Threshold) [PSYoungGen: 172709K->0K(544768K)] [ParOldGen: 230678K->398712K(628224K)] 403387K->398712K(1172992K), [Metaspace: 36781K->36523K(1081344K)], 0.2291498 secs] [Times: user=0.20 sys=1.25, real=0.23 secs]
2019-10-24T15:33:50.564+0800: 13.214: [GC (Allocation Failure) [PSYoungGen: 326144K->88922K(569344K)] 790394K->553180K(1197568K), 0.0217578 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
Heap
PSYoungGen total 569344K, used 303805K [0x000000076ab80000, 0x00000007a2800000, 0x00000007c0000000)
eden space 326144K, 65% used [0x000000076ab80000,0x0000000777d58c70,0x000000077ea00000)
from space 243200K, 36% used [0x000000077ea00000,0x00000007840d6960,0x000000078d780000)
to space 226304K, 0% used [0x0000000794b00000,0x0000000794b00000,0x00000007a2800000)
ParOldGen total 628224K, used 464258K [0x00000006c0200000, 0x00000006e6780000, 0x000000076ab80000)
object space 628224K, 73% used [0x00000006c0200000,0x00000006dc760a18,0x00000006e6780000)
Metaspace used 55000K, capacity 59794K, committed 60032K, reserved 1099776K
class space used 8350K, capacity 10033K, committed 10112K, reserved 1048576K

上面所示内容为设置基本VM Options,启动应用后的GC日志,查看可知在没有指定垃圾收集器的情况下,jdk默认选择了ParallelGC收集器(在没有指定垃圾收集器的时候,并且内存较为充足的情况下,jdk会默认选择ParallelGC)。

2.1 Young GC

截取一条日志进行分析(为了更好的展示,将单挑日志拆分为三段,并使用上标表示每个字段的位置):

2019-10-24T15:33:42.076+08001: 4.7262:

[GC3 (Allocation Failure4) [PSYoungGen5: 65536K->10738K6(132608K7)] 71711K->64480K8(232960K9), 0.0512660 secs10]

[Times: user=0.06 sys=0.42, real=0.05 secs11]

针对此条日志,进行具体分析:

(1) 2019-10-24T15:33:42.076+0800:当前GC事件发生时的UTC时间戳;

(2) 4.726: 当前GC发生时,JVM的启动事件(单位为s);

(3) GC: 用于区分Young GC与Full GC,当前GC为Young GC;

(4) Allocation Failure: 当前GC的产生原因(新生代中空间不足以为新对象分配空间);

(5) PSYoungGen: 垃圾收集器的名称;

(6) 65536K->10738K: GC前后新生代的内存空间使用量;

(7) 132608K: 新生代总内存量;

(8) 71711K->64480K: GC前后应用堆内存的变化情况(包括新生代和老年代);

(9) 232960K: JVM堆的可用内存;

(10) 0.0512660 secs: 本次GC耗时,单位为s;

(11) [Times: user=0.06 sys=0.42, real=0.05 secs]:GC事件的耗时;

  • user: 此次GC,所有GC线程消耗的CPU事件总和;
  • sys: 此次GC,操作系统调用等事件所消耗的时间总和;
  • real: 应用停顿的总时间,在并行的GC中,此数值应该接近(user + sys) / GCThreads,即单核上的平均停顿时间。

综上所述,在GC前总共使用了71711K堆内存,其中新生代使用了65536K,则老年代使用了6175K。GC后,新生代释放了65536-10738=54798K内存,但是堆内存仅释放了71711-64480=7231K内存,说明此次GC,有47567K的对象从新生代升级到了老年代。变化情况如下图所示:

2.2 Full GC

截取一条日志进行分析(为了更好的展示,将单挑日志拆分为三段,并使用上标表示每个字段的位置):

2019-10-24T15:33:46.099+08001: 8.7492:

[Full GC3 (Metadata GC Threshold4) [PSYoungGen5: 172709K->0K6(544768K7)] [ParOldGen8: 230678K->398712K9(628224K10)] 403387K->398712K10(1172992K11),

[Metaspace12: 36781K->36523K13(1081344K14)], 0.2291498 secs15] [Times: user=0.20 sys=1.25, real=0.23 secs]16

针对此条日志,进行具体分析:

(1) 2019-10-24T15:33:46.099+0800: 当前GC事件发生时的UTC时间戳;

(2) 8.749: 当前GC发生时,JVM的启动事件(单位为s);

(3) Full GC: 用于区分Young GC与Full GC,当前GC为Full GC;

(4) Metadata GC Threshold: 当前GC的产生原因(Metaspace空间占用达到阈值);

(5) PSYoungGen: 新生代垃圾收集器名称;

(6) 172709K->0K: Full GC时,新生代内存空间将被清空;

(7) 544768K: 新生代总内存量;

(8) ParOldGen: 老年代垃圾收集器名称;

(9) 230678K->398712K: Full GC时,老年代内存空间变化情况;

(10) 628224K: 老年代总内存量;

(11) 403387K->398712K: GC前后堆内存的变化情况;

(12) 1172992K: JVM堆的可用内存;

(13) Metaspace: metaspace空间;

(14) 36781K->36523K: metaspace空间的变化情况;

(15) 1081344K: metadata的保留空间(metaspace时从jvm进程的虚拟地址空间中分离出来的);

(16) 0.2291498 secs: 当前GC的耗时,单位为s;

(17) [Times: user=0.20 sys=1.25, real=0.23 secs]: 具体含义参见Young GC解释。

由以上数据可知,在此次Full GC的过程中,Young区域的数据被清空,但是Old区以及Metaspace区均进行扩容以便存放更多数据。

3. G1收集器

并发收集器主要有CMS以及G1收集器,因为在jdk 1.8上,G1收集器已经得到较大的完善,因此舍弃CMS收集器的日志分析,直接分析G1收集器(在VM Options中添加-XX:+UseG1GC即可)。如果读者想使用CMS收集器,只需要在VM Options中添加-XX:+UseConcMarkSweepGC即可。

2019-10-24T16:52:43.377+0800: 7.865: [GC pause (G1 Evacuation Pause) (young), 0.0197774 secs]
[Parallel Time: 10.9 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 7865.0, Avg: 7865.0, Max: 7865.1, Diff: 0.1]
[Ext Root Scanning (ms): Min: 0.7, Avg: 1.7, Max: 5.7, Diff: 4.9, Sum: 13.9]
[Update RS (ms): Min: 0.0, Avg: 1.0, Max: 2.4, Diff: 2.4, Sum: 7.8]
[Processed Buffers: Min: 0, Avg: 11.3, Max: 29, Diff: 29, Sum: 90]
[Scan RS (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 2.7]
[Code Root Scanning (ms): Min: 0.0, Avg: 2.1, Max: 8.3, Diff: 8.3, Sum: 16.9]
[Object Copy (ms): Min: 0.0, Avg: 5.4, Max: 7.8, Diff: 7.8, Sum: 43.3]
[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.7]
[Termination Attempts: Min: 1, Avg: 2.4, Max: 4, Diff: 3, Sum: 19]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[GC Worker Total (ms): Min: 10.6, Avg: 10.7, Max: 10.7, Diff: 0.1, Sum: 85.6]
[GC Worker End (ms): Min: 7875.7, Avg: 7875.7, Max: 7875.8, Diff: 0.1]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.3 ms]
[Other: 8.6 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 7.6 ms]
[Ref Enq: 0.1 ms]
[Redirty Cards: 0.2 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.3 ms]
[Eden: 161.0M(161.0M)->0.0B(182.0M) Survivors: 13.0M->21.0M Heap: 626.5M(1066.0M)->474.0M(1066.0M)]
[Times: user=0.05 sys=0.00, real=0.02 secs]

G1的垃圾收集过程,因为其特殊的内存划分,导致存在很多细微的步骤。

我们截取其主要部分进行分析:

(1) [GC pause (G1 Evacuation Pause) (young), 0.0197774 secs]: 说明当前GC为Young GC,cpu耗时0.0197774s;

(2) [Parallel Time: 10.9 ms, GC Workers: 8]: 此次GC停顿的实际时间为10.9ms,共有8个线程参与清理工作;

(3) Eden: 161.0M(161.0M)->0.0B(182.0M) Survivors: 13.0M->21.0M Heap: 626.5M(1066.0M)->474.0M(1066.0M):

此段说明Eden区的内存共计161.0M被释放了,有部分对象转移至Survivors区导致Survivors区内存扩大,堆内存因为Eden区释放资源的缘故总量变小(空间未发生变化)。

因为在当前的GC日志抓取中,未存在Mixed GC,因此此处就不对Mixed GC日志进行分析(感兴趣的读者可以在较大的程序中获取G1 GC日志进行进一步分析)。虽然G1的GC日志虽然步骤很清晰,但是每次GC均会有大量日志产生,不便于根据日志进行分析,因此我推荐使用GCViewer可视化工具辅助进行分析。导入GC日志,信息如下图所示:

图中左侧详细罗列了GC的详细情况,包括GC Pause(停顿应用的GC数据)以及Concurrent GCs(与应用并发执行的GC数据)。右侧则概要的描述了应用开始执行后的吞吐量(Throughput:95.15%),导致应用停顿的GC次数(Number of gc pauses:8)以及GC停顿时间(Accumulated pauses: 0.36s)。通过以上信息,就可以对G1收集器在应用执行过程的表现有一个清晰的认识。

3. 性能优化

直接根据GC日志进行针对性调优较为困难(生产环境的GC日志量比较大,尤其是CMS/G1的GC日志),因此建议将GC日志导出,通过图形化工具GCViewer进行汇总统计,得到可靠的报表信息。

使用GCViewer进行调优时,一般关注以下几个指标:Throughput(吞吐量),Number of gc pauses(GC停顿数量)以及Accumulated pauses(GC停顿总耗时)。

3.1 Parallel收集器调优

调优参数:

  • -XX:MetaspaceSize: 在应用启动初期,普遍存在因为metaspace空间达到阈值引发的GC,因此可以合理设置Metaspace空间规避此类GC;
  • -XX:MaxGCPauseMillis: 最大GC停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿小于这个时间;
  • -XX:GCTimeRatio: 垃圾收集时间占总时间的比例(假设GCTimeRatio设置为N,则垃圾收集时间占总时间的比例为1 / (1 + N))

Parallel收集器是自适应收集器,一般设置MaxGCPauseMillis以及GCTimeRatio,即可由jdk进行动态调整,用户可以根据GC日志得到最佳的停顿时间和吞吐量设置方案。

3.2 G1收集器调优

关于G1垃圾收集器的调优,建议有以下两条:

  • 年轻代大小:避免使用-Xmn选项或-XX:NewRatio 等其他相关选项显式设置年轻代大小,因为固定年轻代的大小会覆盖暂停时间目标。
  • 暂停时间目标:每当对垃圾回收进行评估或调优时,都会涉及到延迟与吞吐量的权衡。G1是增量垃圾回收器,其吞吐量目标是90% 的应用程序时间和10%的垃圾回收时间。因此,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。

如果对于性能有着严苛的要求,可以参照https://toutiao.io/k/8b0dxca的做法,开启G1的所有参数:

-Xmx30g -Xms30g
-XX:MaxDirectMemorySize=30g
-XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions
-XX:MaxGCPauseMillis=90
-XX:G1NewSizePercent=8
-XX:InitiatingHeapOccupancyPercent=30
-XX:+ParallelRefProcEnabled
-XX:ConcGCThreads=4
-XX:ParallelGCThreads=16
-XX:MaxTenuringThreshold=1
-XX:G1HeapRegionSize=32m
-XX:G1MixedGCCountTarget=64
-XX:G1OldCSetRegionThresholdPercent=5

其中重点需要调优的参数有:

  • G1NewSizePercent :G1的Young区大小是通过算法来自适应确定的, 也就是根据之前Young区GC的耗时来确定之后的Young大小,如果耗时过长,则调小Young区,耗时过短,则调大Young区. 这个参数表示Young的最小百分比;
  • InitiatingHeapOccupancyPercent: 当占用内存超过这个百分比的时候, G1开始执行多次Mixed GC来整理老年代内存碎片;
  • G1MixedGCCountTarget: 当占用内存超过InitiatingHeapOccupancyPercent阀值时, 最多通过多少次Mixed GC来将内存控制在阀值之下;
  • MaxTenuringThreshold: 当一个对象gc的代数超过这个值的时候, 会将对象从young区挪到old区;
  • G1HeapRegionSize: 表示G1将每个Region切分成多大, 注意一定要写单位, 例如32m。

逐个调整以上参数并收集GC日志,通过GCViewer收集各项指标,最终根据实际情况挑选满意的G1收集器参数即可。

PS:

如果您觉得我的文章对您有帮助,请关注我的微信公众号,谢谢!

Java垃圾收集器——Parallel、G1收集器日志分析及性能调优示范的更多相关文章

  1. JVM垃圾收集器-Parallel Scavenge收集器

    今天我给大家讲讲JVM垃圾收集器-Parallel Scavenge收集器 Parallel Scavenge收集器 Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的 ...

  2. CMS收集器和G1收集器优缺点

    首先要知道 Stop the world的含义(网易面试):不管选择哪种GC算法,stop-the-world都是不可避免的.Stop-the-world意味着从应用中停下来并进入到GC执行过程中去. ...

  3. JVM-如何判断对象存活与否与CMS收集器和G1收集器的区别

    JVM如何判断对象存活? 1.计数器 2.可达性分析   (很多主流语言采用这种方法来判断对象是否存活) 计数器:每当有一个地方引用该对象时,计数器 +1:引用失效则 -1: 优点:实现简单,判定效率 ...

  4. CMS收集器和G1收集器 他们的优缺点对比 G1只有并发标记才不会stop-the-world 其他都会停下来(阿里多次问到)

    CMS收集算法 参考:图解 CMS 垃圾回收机制原理,-阿里面试题 G1收集算法 参考:G1 垃圾收集器入门 首先要知道 Stop the world的含义(网易面试):不管选择哪种GC算法,stop ...

  5. CMS垃圾收集器与G1收集器

    1.CMS收集器 CMS收集器是一种以获取最短回收停顿时间为目标的收集器.基于“标记-清除”算法实现,它的运作过程如下: 1)初始标记 2)并发标记 3)重新标记 4)并发清除 初始标记.从新标记这两 ...

  6. 垃圾收集器之:G1收集器

    G1垃圾收集器是一种工作在堆内不同分区上的并发收集器.分区既可以归属于老年代,也可以归属新生代,同一个代的分区不需要保持连续.为老年代设计分区的初衷是我们发现并发后台线程在回收老年代中没有引用的对象时 ...

  7. CMS收集器和G1收集器

    CMS收集器 CMS收集器是一种以获取最短回收停顿时间为目标的收集器.基于"标记-清除"算法实现,它的运作过程如下: 初始标记 并发标记 重新标记 并发清除 初始标记.从新标记这两 ...

  8. G1收集器-原创译文[未完成]

    G1收集器-原创译文 原文地址 Getting Started with the G1 Garbage Collector 目的 本文介绍了如何使用G1垃圾收集器以及如何与Hotspot JVM一起使 ...

  9. G1收集器

    转载:https://blog.csdn.net/zhou2s_101216/article/details/79202893 http://blog.jobbole.com/109170/ http ...

随机推荐

  1. linux中网络配置

    一.查看ip ipconfig 二.png 测试主机之间网络连通 ping 目标ip #测试当前服务器是否可以连接目的主机 三.linux配置网络环境 vim /etc/sysconfig/netwo ...

  2. Evaluation Warning : The document was created with Spire.PDF for .NET.

    由于使用  Spire.Pdf 生成的书签带有 Evaluation Warning : The document was created with Spire.PDF for .NET. 字样 但是 ...

  3. Inherit from the Business Class Library Class 继承自Business类(EF)

    In this lesson, you will learn how to implement business classes for your application using the Busi ...

  4. log4j配置项

    log4j 配置文件log4j.rootLogger=INFO,console,dailyFile# 控制台配置项log4j.appender.console=org.apache.log4j.Con ...

  5. Thymeleaf常用语法:条件判断 if、switch case

    if语句条件判断使用th:if,它会判断表达式是否成立,表达式的结果支持boolean.number.character.String及其他类型.满足下面情况,if语句成立:(1) 表达式的结果是数字 ...

  6. java 读取 excel 表格内容

    一.添加依赖 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</ar ...

  7. 10. java 匿名对象说明

    一.匿名对象 public class Demo{ public static void main(String[] args){ Person one = new Person(); one.nam ...

  8. VIJOS-P1013 强墙

    JDOJ 1198: VIJOS-P1013 强墙 JDOJ传送门 Description ​ 在一个长宽均为10,入口出口分别为(0,5).(10,5)的房间里,有几堵墙,每堵墙上有两个缺口,求入口 ...

  9. POJ2001Shortest Prefixes(Trie树)

    传送门 题目大意:求最短唯一前缀 题解:Trie树 把单词一个个插入,每个字母节点v[]++;然后输出时输出到v[]为1的点, v[]=1说明只有这个单词经过. 代码 : #include<io ...

  10. event.stopPropagation()和event.preventDefault()

    1.event.stopPropagation()方法 这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开, ...