境里婆娑 于 2020-04-25 18:52:32 发布
提起看GC日志大部分同学可能都会皱起眉头不知道如何看、什么时候看以及GC日志如何帮助我们调优JVM等。本篇文章将讲解主流垃圾回收器G1,目前Java官方推荐使用G1,所以学看G1垃圾回收日志迫在眉睫。
在学习查看G1 GC日志之前,如果对G1不是很了解,可以看这篇文章,讲的非常详细。
传送门: G1垃圾收集器简介
G1的日志参数分为等级递增的三块,这篇文章将会分别介绍每一部分参数的作用和调优时候使用的场景。
在idea中配置启动参数 VM options: -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseG1GC

如果你要在生产环境中使用G1 GC,下面这些跟日志相关的参数是必备的,有了这些参数,你才能排查基本的垃圾回收问题。
| -Xloggc:/path/to/gc.log | 写入GC日志的路径 |
|---|---|
| -XX:+UseGCLogFileRotation | 启用GC日志文件轮换 |
| -XX:NumberOfGCLogFiles = | 要保留的旋转GC日志文件数 |
| -XX:GCLogFileSize = | 每个GC日志文件的大小以启动轮换 |
| -XX:+ PrintGCDetial | 详细的GC日志 |
| -XX:+ PrintGCDateStamps | 每次GC时会打印程序启动后至GC发生的时间戳 |
| -XX:+ PrintGCApplicationStoppedTime | GC期间应用程序停止的时间 |
| -XX:+ PrintGCApplicationConcurrentTime | 应用程序在GC之间运行的时间 |
| -XX:-PrintCommandLineFlags | 在GC日志中打印所有命令行标志 |
| -XX:MaxGCPauseMillis=n | 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标. |
| XX:InitiatingHeapOccupancyPercent=n | 启动并发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:G1ReservePercent=n | 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10. |
| -XX:G1HeapRegionSize=n | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb. |
使用-XX:GCLogFileSize设置合适的GC日志文件大小,使用-XX:NumberOfGCLogFiles设置要保留的GC日志文件个数,使用-Xloggc:/path/to/gc.log设置GC日志文件的位置,通过上面三个参数保留应用在运行过程中的GC日志信息,我建议最少保留一个星期的GC日志,这样应用的运行时信息足够多的,方便排查问题。
和其他垃圾收集器一样,G1也使用-XX:PrintGCDetails打印出详细的垃圾收集日志,下面是新生代收集的标准流程,我在这里将它分成了6个步骤:
1 2020-04-25T15:36:17.135+0800: [GC pause (G1 Evacuation Pause) (young), 0.0044414 secs] 2 [Parallel Time: 3.4 ms, GC Workers: 4] [GC Worker Start (ms): Min: 8782.3, Avg: 8783.2, Max: 8785.7, Diff: 3.3] [Ext Root Scanning (ms): Min: 0.0, Avg: 0.4, Max: 0.6, Diff: 0.6, Sum: 1.6] [Update RS (ms): Min: 0.0, Avg: 1.0, Max: 1.5, Diff: 1.5, Sum: 4.1] [Processed Buffers: Min: 0, Avg: 2.5, Max: 6, Diff: 6, Sum: 10] [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] [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: 1.0, Max: 1.3, Diff: 1.3, Sum: 3.9] [Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.2] [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1] [GC Worker Total (ms): Min: 0.0, Avg: 2.5, Max: 3.3, Diff: 3.3, Sum: 9.9] [GC Worker End (ms): Min: 8785.7, Avg: 8785.7, Max: 8785.7, Diff: 0.0] 3 [Code Root Fixup: 0.0 ms] [Code Root Migration: 0.0 ms] [Clear CT: 0.0 ms] 4 [Other: 1.0 ms] [Choose CSet: 0.0 ms] [Ref Proc: 0.6 ms] [Ref Enq: 0.0 ms] [Free CSet: 0.0 ms] 5 [Eden: 4096.0K(4096.0K)->0.0B(3072.0K) Survivors: 1024.0K->1024.0K Heap: 23.9M(28.0M)->20.4M(28.0M)] 6 [Times: user=0.00 sys=0.02, real=0.01 secs] Eden:4096.0K(4096.0K)->0.0B(3072.0K):(1)当前新生代收集触发的原因是Eden空间满了,分配了4096K,使用了4096K;(2)所有的Eden分区都被疏散处理了,在新生代结束后Eden分区的使用大小成为了0.0B;(3)Eden分区的大小缩小为3072K Survivors:1024.0K->1024.0K:由于年轻代分区的无回收处理,survivor的空间不变。 Heap: 23.9M(28.0M)->20.4M(28.0M):(1)在本次垃圾收集活动开始的时候,堆空间整体使用量是23.9M,堆空间的最大值是28.0M;(2)在本次垃圾收集结束后,堆空间的使用量是20.4M,最大值保持不变。
G1的第二种收集活动是并发垃圾收集,并发垃圾收集的触发条件有很多,但是做的工作都相同,它的日志所示:
xxxxxxxxxx1 2020-04-25T15:36:17.590+0800: [GC pause (G1 Evacuation Pause) (young),(initial-mark), 0.0043944 secs]2 2020-04-25T15:36:17.590+0800: [GC concurrent-root-region-scan-start] 2020-04-25T15:36:17.594+0800: [GC concurrent-root-region-scan-end, 0.0043944 secs]3 2020-04-25T15:36:17.594+0800: [GC concurrent-mark-start] 2020-04-25T15:36:17.640+0800: [GC concurrent-mark-end, 0.0460160 secs]4 2020-04-25T15:36:17.640+0800: [GC remark [GC ref-proc, 0.0028598 secs], 0.0130111 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 5 2020-04-25T15:36:17.654+0800: [GC cleanup 18M->18M(56M), 0.0001847 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 6 2020-04-25T15:36:17.890+0800: [GC concurent-cleanup-stat] 2020-04-25T15:36:17.904+0800: [GC concurrent-cleanup-end, 0.0001847 secs]如果堆内存空间不足以分配新的对象,或者是Metasapce空间使用率达到了设定的阈值,那么就会触发Full GC——你在使用G1的时候应该尽量避免这种情况发生,因为G1的Full Gc是单线程、会Stop The World,代价非常高。Full GC的日志如下所示,从中你可以看出三类信息
xxxxxxxxxx2020-04-25T18:00:11.267+0800: [Full GC (Allocation Failure) 1520M->918M(1886M), 5.1284574 secs] [Eden: 0.0B(93.0M)->0.0B(94.0M) Survivors: 1024.0K->0.0B Heap: 1520.8M(1886.0M)->918.5M(1886.0M)], [Metaspace: 39714K->39714K(1085440K)] [Times: user=5.02 sys=0.00, real=5.13 secs] 2020-04-25T18:00:16.395+0800: [Full GC (Allocation Failure) 918M->916M(1886M), 4.7699272 secs] [Eden: 0.0B(94.0M)->0.0B(94.0M) Survivors: 0.0B->0.0B Heap: 918.5M(1886.0M)->916.1M(1886.0M)], [Metaspace: 39714K->39488K(1085440K)] [Times: user=4.73 sys=0.00, real=4.77 secs] 基础配置参数中,我这里还想介绍两个:-XX:+PrintGCApplicationStoppedTime和-XX:+PrintGCApplicationConcurrentTime,这两个参数也可以为你提供有用的信息,如下所示
xxxxxxxxxx1 2020-04-25T18:00:11.266+0800: Total time for which application threads were stopped: 0.0024315 seconds2 2020-04-25T18:00:11.266+0800: Application time: 0.0000549 seconds1 、记录了应用线程在安全点被暂停的总时间(也就是STW的总时间) 2、 记录了在两个安全点之间应用线程运行的时间
本篇文章参考:Collecting and reading G1 garbage collector logs - part 2