我求求你了,GC日志打印别再瞎配置了

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 生产环境上,或者其他要测试 GC 问题的环境上,一定会配置上打印GC日志的参数,便于分析 GC 相关的问题。但是可能很多人配置得都不够“完美”,要么是打印的内容过少,要么是输出到控制台,要么是一个大文件被覆盖,要么是……本文带你一步一步,配置一个完美的 GC 日志打印策略

网络异常,图片无法展示
|

生产环境上,或者其他要测试 GC 问题的环境上,一定会配置上打印GC日志的参数,便于分析 GC 相关的问题。

但是可能很多人配置得都不够“完美”,要么是打印的内容过少,要么是输出到控制台,要么是一个大文件被覆盖,要么是……

本文带你一步一步,配置一个完美的 GC 日志打印策略

打印内容

为了保留足够多的“现场证据”,最好是把 GC 相关的信息打印的足够完整。而且你的程序真的不差你GC时打印日志I/O消耗的那点性能

打印基本 GC 信息

打印 GC 日志的第一步,就是开启 GC 打印的参数了,也是最基本的参数。

-XX:+PrintGCDetails -XX:+PrintGCDateStamps

打印对象分布

为了分析 GC 时的晋升情况和晋升导致的高暂停,不看对象年龄分布日志怎么行

-XX:+PrintTenuringDistribution

输出内容示例:

Desired survivor size 59244544 bytes, new threshold 15 (max 15)
- age   1:     963176 bytes,     963176 total
- age   2:     791264 bytes,    1754440 total
- age   3:     210960 bytes,    1965400 total
- age   4:     167672 bytes,    2133072 total
- age   5:     172496 bytes,    2305568 total
- age   6:     107960 bytes,    2413528 total
- age   7:     205440 bytes,    2618968 total
- age   8:     185144 bytes,    2804112 total
- age   9:     195240 bytes,    2999352 total
- age  10:     169080 bytes,    3168432 total
- age  11:     114664 bytes,    3283096 total
- age  12:     168880 bytes,    3451976 total
- age  13:     167272 bytes,    3619248 total
- age  14:     387808 bytes,    4007056 total
- age  15:     168992 bytes,    4176048 total

GC 后打印堆数据

每次发生 GC 时,对比一下 GC 前后的堆内存情况,更直观

-XX:+PrintHeapAtGC

输出内容示例:

{Heap before GC invocations=0 (full 0):
 garbage-first heap   total 1024000K, used 324609K [0x0000000781800000, 0x0000000781901f40, 0x00000007c0000000)
  region size 1024K, 6 young (6144K), 0 survivors (0K)
 Metaspace       used 3420K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 371K, capacity 388K, committed 512K, reserved 1048576K
Heap after GC invocations=1 (full 1):
 garbage-first heap   total 1024000K, used 21755K [0x0000000781800000, 0x0000000781901f40, 0x00000007c0000000)
  region size 1024K, 0 young (0K), 0 survivors (0K)
 Metaspace       used 3420K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 371K, capacity 388K, committed 512K, reserved 1048576K
}

打印 STW 时间

暂停时间是 GC 最重要的指标,肯定不能少

-XX:+PrintGCApplicationStoppedTime

输出内容示例:

Total time for which application threads were stopped: 0.0254260 seconds, Stopping threads took: 0.0000218 seconds

打印 safepoint 信息

进入STW阶段之前,需要要找到一个合适的 safepoint ,这个指标一样很重要(非必选,出现 GC 问题时最好加上此参数调试)

-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1

输出内容示例:

vmop                    [threads: total initially_running wait_to_block]    [time: spin block sync cleanup vmop] page_trap_count
0.371: ParallelGCFailedAllocation       [      10          0              0    ]      [     0     0     0     0     7    ]  0   
Execute full gc...dataList has been promoted to cms old space
         vmop                    [threads: total initially_running wait_to_block]    [time: spin block sync cleanup vmop] page_trap_count
0.379: ParallelGCSystemGC               [      10          0              0    ]      [     0     0     0     0    16    ]  0   
         vmop                    [threads: total initially_running wait_to_block]    [time: spin block sync cleanup vmop] page_trap_count
0.396: no vm operation                  [       9          1              1    ]      [     0     0     0     0   341    ]  0

打印 Reference 处理信息

强引用/弱引用/软引用/虚引用/finalize 方法万一有问题,不得打印出来看看?

-XX:+PrintReferenceGC

输出内容示例:

2021-02-19T12:41:30.462+0800: 5072726.605: [SoftReference, 0 refs, 0.0000521 secs]
2021-02-19T12:41:30.462+0800: 5072726.605: [WeakReference, 0 refs, 0.0000069 secs]
2021-02-19T12:41:30.462+0800: 5072726.605: [FinalReference, 0 refs, 0.0000056 secs]
2021-02-19T12:41:30.462+0800: 5072726.605: [PhantomReference, 0 refs, 0 refs, 0.0000059 secs]
2021-02-19T12:41:30.462+0800: 5072726.605: [JNI Weak Reference, 0.0000131 secs], 0.4635293 secs]

完整参数

# requireds
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+PrintTenuringDistribution 
-XX:+PrintHeapAtGC 
-XX:+PrintReferenceGC 
-XX:+PrintGCApplicationStoppedTime
# optional
-XX:+PrintSafepointStatistics 
-XX:PrintSafepointStatisticsCount=1

输出方式

上面只是定义了打印的内容,默认情况下,这些日志会输出到控制台(标准输出)。那如果你的程序日志也输出到控制台呢,这个日志内容就会很乱,分析起来很麻烦。如果你是追加的方式(比如 tomcat 的 catalina.out 就是追加),这个文件会越来越大,分析起来就要命了。

所以需要一种分割日志的机制,这个机制嘛……JVM自然是提供的。

JVM 的日志分割

JVM提供了几个用于分割 GC 日志的参数:

# GC日志输出的文件路径
-Xloggc:/path/to/gc.log
# 开启日志文件分割
-XX:+UseGCLogFileRotation 
# 最多分割几个文件,超过之后从头开始写
-XX:NumberOfGCLogFiles=14
# 每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=100M

按照这个参数,每个GC日志只要超过20M就会进行分割,最多分割5个文件,文件名依次是gc.log.0,gc.log.1,gc.log.2,gc.log.3,gc.log.4, .....

看似很美好,几行配置就搞定了输出文件的问题。但是这种方式有一些问题:

  1. -Xloggc 方式指定的日志文件,是覆盖写的方式,每次启动都会覆盖,历史日志会丢失
  2. 当超过最大分割数后,会从第0个文件开始重新写入,而且是覆盖
  3. -XX:NumberOfGCLogFiles 并不能设置为无限

这个覆盖的问题就有点恶心了,每次启动覆盖之前的历史日志……这谁能忍?

使用时间戳命名文件

于是有另一种解决方案。不使用 JVM 提供的日志分割功能,而是每次启动用时间戳命名日志文件,这样可以每次启动都使用不同的文件,就不会出现覆盖的问题了。

# 使用-%t作为日志文件名
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc-%t.log
# 生成的文件名是这种:gc-2021-03-29_20-41-47.log

可是这样就完美吗?

虽然没有覆盖的问题,但由于没有日志分割的功能,每次启动后只有一个GC日志文件,单个日志文件可能会非常巨大。过大的日志文件分析起来是很麻烦的,必须得分割。

二者结合

这里只需要稍微调整一下策略,将 JVM 分割和时间戳命名两种方案结合,就可以得到最优的方式了。

# GC日志输出的文件路径
-Xloggc:/path/to/gc-%t.log
# 开启日志文件分割
-XX:+UseGCLogFileRotation 
# 最多分割几个文件,超过之后从头开始写
-XX:NumberOfGCLogFiles=14
# 每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=100M

配置时间戳作文 GC 日志文件名的同时,也配置JVM的GC日志分割策略。这样一来,既保证了 GC 文件不会被覆盖,又保证了单个 GC 文件的大小不会过大,完美!

最终得到的日志文件名会像这个样子:

  1. gc-2021-03-29_20-41-47.log.0
  2. gc-2021-03-29_20-41-47.log.1
  3. gc-2021-03-29_20-41-47.log.2
  4. gc-2021-03-29_20-41-47.log.3
  5. ....

最佳实践 - 完整参数

# 必备
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+PrintTenuringDistribution 
-XX:+PrintHeapAtGC 
-XX:+PrintReferenceGC 
-XX:+PrintGCApplicationStoppedTime
# 可选
-XX:+PrintSafepointStatistics 
-XX:PrintSafepointStatisticsCount=1
# GC日志输出的文件路径
-Xloggc:/path/to/gc-%t.log
# 开启日志文件分割
-XX:+UseGCLogFileRotation 
# 最多分割几个文件,超过之后从头文件开始写
-XX:NumberOfGCLogFiles=14
# 每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=100M

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
26天前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
2月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
51 3
|
2月前
|
存储 数据可视化 安全
一个简单案例,带你看懂GC日志!
一个简单案例,带你看懂GC日志!
一个简单案例,带你看懂GC日志!
|
5月前
|
算法 安全 Java
(七)JVM成神路之GC分代篇:分代GC器、CMS收集器及YoungGC、FullGC日志剖析
在《GC基础篇》中曾谈到过分代以及分区回收的概念,但基础篇更多的是建立在GC的一些算法理论上进行高谈阔论,而本篇则重点会对于分代收集器的实现进行全面详解,其中会涵盖串行收集器、并行收集器、三色标记、SATB算法、GC执行过程、并发标记、CMS收集器等知识,本篇则偏重于分析GC机制的落地实现,也就是垃圾收集器(Garbage Collector)。
117 8
|
6月前
|
Java
GC日志打印
可以看到所有的FULL GC已经没有了,时间节省上面,大于是 0.03*3 约等于 0.1秒。
50 0
|
7月前
|
运维 监控 数据可视化
九、GC收集日志
九、GC收集日志
71 4
九、GC收集日志
|
7月前
|
算法 Java
深入理解JVM - 解读GC日志
深入理解JVM - 解读GC日志
94 0
|
7月前
|
存储 Java
jvm性能调优实战 - 23 模拟Young GC的发生及分析GC日志
jvm性能调优实战 - 23 模拟Young GC的发生及分析GC日志
107 0
|
Java
JVM学习笔记-如何在IDEA打印JVM的GC日志信息
若要在Idea上打印JVM相应GC日志,其实只需在Run/Debug Configurations上进行设置即可。
116 0
|
缓存 安全 Java
JVM中垃圾回收相关参数介绍:大页和NUMA参数+GC日志相关参数
大页和NUMA参数 本节介绍JVM为使用OS而提供的大页和NUMA特性相关的参数。 该参数控制JVM向OS请求内存时使用大页的粒度。使用该参数时需要对OS进行配置,只有OS允许时才能真正启动。参数的默认值与平台相关,一般为false。 在允许使用大页方式向OS请求内存时,如果堆空间小于该阈值,则强制禁止大页使用。该参数的默认值为128MB。 在允许使用大页方式向OS请求内存时,优先在本地节点进行分配。该参数仅适用于Windows系统。 在允许使用大页方式向OS请求内存时,如果OS提供了多种大页的设置,可通过该参数选择其中的大页设置。参数的默认值为0,表示使用OS默认的大页设置。
179 0