GC(Garbage Collection)日志是 JVM 提供的用于记录垃圾回收过程的详细信息的文件。通过分析 GC 日志,可以了解 JVM 的内存使用情况、GC 的频率、停顿时间、回收效率等,从而帮助我们诊断和解决 GC 相关的性能问题。
1. 开启 GC 日志:
-
常用 JVM 参数:
-XX:+PrintGC
: 打印 GC 基本信息(已过时,但仍然可以使用)。-XX:+PrintGCDetails
: 打印 GC 详细信息(推荐)。-XX:+PrintGCDateStamps
: 在 GC 日志中打印日期和时间戳。-XX:+PrintGCTimeStamps
: 在 GC 日志中打印时间戳(相对于 JVM 启动时间的秒数)。-Xloggc:<file>
: 将 GC 日志输出到指定文件(例如-Xloggc:gc.log
)。- 文件轮转 (File Rotation): 可以使用
-XX:+UseGCLogFileRotation
、-XX:NumberOfGCLogFiles
、-XX:GCLogFileSize
等参数配置 GC 日志文件轮转。
- 文件轮转 (File Rotation): 可以使用
-XX:+PrintGCApplicationStoppedTime
: 打印应用程序由于 GC 导致的停顿时间。-XX:+PrintGCApplicationConcurrentTime
: 打印应用程序在两次 GC 之间的运行时间.-XX:+PrintHeapAtGC
: 在每次 GC 前后打印堆的详细信息。- **
-XX:+PrintTenuringDistribution
: ** 打印 Survivor 区中对象的年龄分布. -Xlog
(JDK 9+): 统一日志框架 (Unified Logging Framework). 功能更强大, 配置更灵活.
-
示例:
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar myapp.jar
# JDK 9+ java -Xlog:gc*:file=gc.log:time,level,tags -jar myapp.jar
2. GC 日志格式 (HotSpot VM):
GC 日志的格式取决于 JVM 版本和使用的垃圾收集器,没有统一的标准格式。以下是一些常见的 GC 日志信息:
-
GC 类型:
- Minor GC (Young GC): 新生代垃圾回收。
[GC
或[DefNew
(Serial),[ParNew
(ParNew),[PSYoungGen
(Parallel Scavenge)
- Major GC (Old GC): 老年代垃圾回收。
[Full GC
(Serial Old, Parallel Old, CMS),[CMS
(CMS),[G1 Old GC
,[ZGC
- Full GC: 对整个堆(包括新生代和老年代)进行垃圾回收。
[Full GC
- Minor GC (Young GC): 新生代垃圾回收。
-
时间信息:
- 时间戳: GC 发生的时间(例如,
2023-10-27T10:00:00.123+0800
)。 - JVM 启动时间: GC 发生的时间(相对于 JVM 启动时间的秒数,例如
1.234s
)。
- 时间戳: GC 发生的时间(例如,
-
内存信息:
- GC 前堆使用情况:
[PSYoungGen: 100M->10M(150M)]
表示 GC 前新生代使用了 100MB,剩余 10MB,新生代总大小为 150MB。 - GC 后堆使用情况:
[ParOldGen: 200M->180M(300M)]
表示 GC 前老年代使用了 200MB,GC 后使用了 180MB,老年代总大小为 300MB。 - 总堆使用情况:
250M->190M(450M)
表示 GC 前整个堆使用了 250MB,GC 后使用了 190MB,堆总大小为 450MB。
- GC 前堆使用情况:
-
GC 耗时: 例如
0.0123456 secs
(单位:秒)。 -
其他信息:
- 垃圾回收原因 (Allocation Failure, Ergonomics, Metadata GC Threshold, System.gc(), etc.)
- 并发标记阶段信息 (CMS, G1)
- Region 信息 (G1)
- 对象年龄分布 (Tenuring Distribution)
GC 日志示例 (不同收集器):
-
Serial/Parallel GC:
[GC (Allocation Failure) [PSYoungGen: 57344K->6688K(62464K)] 57344K->18352K(202752K), 0.0194387 secs] [Times: user=0.06 sys=0.00, real=0.02 secs] [Full GC (Ergonomics) [PSYoungGen: 6688K->0K(62464K)] [ParOldGen: 11664K->16366K(140288K)] 18352K->16366K(202752K), [Metaspace: 34567K->34567K(1081344K)], 0.1019658 secs] [Times: user=0.31 sys=0.04, real=0.11 secs]
-
CMS:
[GC (CMS Initial Mark) [1 CMS-initial-mark: 16366K(140288K)] 18704K(202752K), 0.0008393 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-mark-start] [CMS-concurrent-mark: 0.005/0.005 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [CMS-concurrent-preclean-start] [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [CMS-concurrent-abortable-preclean-start] [CMS-concurrent-abortable-preclean: 0.005/0.159 secs] [Times: user=0.29 sys=0.03, real=0.16 secs] [GC (CMS Final Remark) [YG occupancy: 21947 K (62464 K)]2023-10-27T16:22:32.864+0800: 3.886: [Rescan (parallel) , 0.0176854 secs]2023-10-27T16:22:32.882+0800: 3.903: [weak refs processing, 0.0000398 secs]2023-10-27T16:22:32.882+0800: 3.903: [class unloading, 0.0042382 secs]2023-10-27T16:22:32.886+0800: 3.908: [scrub symbol table, 0.0053587 secs]2023-10-27T16:22:32.892+0800: 3.913: [scrub string table, 0.0004727 secs][1 CMS-remark: 16366K(140288K)] 38313K(202752K), 0.0298176 secs] [Times: user=0.09 sys=0.00, real=0.03 secs] [CMS-concurrent-sweep-start] [CMS-concurrent-sweep: 0.013/0.013 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] [CMS-concurrent-reset-start] [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
G1:
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0017932 secs][Parallel Time: 1.4 ms, GC Workers: 8]...[Eden: 128.0M(128.0M)->0.0B(112.0M) Survivors: 0.0B->16.0M Heap: 128.0M(2048.0M)->42.1M(2048.0M)][Times: user=0.01 sys=0.00, real=0.00 secs] [GC concurrent-root-region-scan-start] ... [GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 0.0042688 secs][Parallel Time: 3.8 ms, GC Workers: 8]...[Eden: 0.0B(112.0M)->0.0B(112.0M) Survivors: 16.0M->16.0M Heap: 177.1M(2048.0M)->162.4M(2048.0M)][Times: user=0.02 sys=0.00, real=0.01 secs] [GC pause (G1 Evacuation Pause) (mixed), 0.0026325 secs]...[Eden: 0.0B(112.0M)->0.0B(128.0M) Survivors: 16.0M->0.0B Heap: 208.4M(2048.0M)->194.1M(2048.0M)][Times: user=0.01 sys=0.00, real=0.00 secs] [GC cleanup 194M->194M(2048M), 0.0004077 secs][Times: user=0.00 sys=0.00, real=0.00 secs]
3. 分析 GC 日志:
- 关注点:
- GC 频率: GC 发生的频率是否过高。
- GC 停顿时间: 每次 GC 暂停了多长时间 (STW)。
- GC 类型: 是 Minor GC、Major GC 还是 Full GC。
- 内存使用情况: GC 前后堆内存、新生代、老年代的使用情况。
- 垃圾回收原因: 了解触发 GC 的原因。
- 对象年龄分布: (如果开启了
-XX:+PrintTenuringDistribution
) 查看对象在 Survivor 区的年龄分布, 了解对象晋升老年代的情况.
- 分析方法:
- 手动分析: 阅读 GC 日志,理解每一行的含义,找出潜在的问题。
- 使用工具分析:
- GCViewer: 一个开源的 GC 日志可视化分析工具。
- gceasy.io: 一个在线的 GC 日志分析工具。
- GChisto: 一个命令行 GC 日志分析工具。
- VisualVM, JProfiler 等: 可以实时监控GC情况.
- 关键指标:
- 吞吐量: 应用程序运行时间占总时间的比例(越高越好)。
- 停顿时间: GC 导致的应用程序暂停时间(越低越好)。
- GC 频率: GC 发生的频率(越低越好,但也要避免停顿时间过长)。
- 内存占用: 应用程序使用的内存量。
4. 常见 GC 问题及原因:
-
频繁 Minor GC:
- 原因:
- 对象分配速率过高。
- 新生代空间不足。
- 解决方案:
- 优化代码,减少对象创建。
- 增大新生代空间 (
-Xmn
)。 - 调整 Eden 区和 Survivor 区的比例 (
-XX:SurvivorRatio
)。
- 原因:
-
频繁 Full GC:
- 原因:
- 老年代空间不足。
- 方法区/元空间不足。
- 大对象过多。
- 内存泄漏。
- 显式调用
System.gc()
。
- 解决方案:
- 增大堆内存 (
-Xmx
)。 - 增大方法区/元空间 (
-XX:MaxMetaspaceSize
)。 - 避免创建过大的对象。
- 使用分析工具 (例如,MAT) 查找内存泄漏。
- 禁用
System.gc()
调用 (-XX:+DisableExplicitGC
)。 - 选择合适的垃圾回收器(例如,G1、CMS)。
- 调整垃圾回收器参数。
- 增大堆内存 (
- 原因:
-
GC 停顿时间过长:
- 原因:
- 堆内存过大。
- 垃圾回收器选择不当。
- 垃圾回收参数设置不合理。
- 解决方案:
- 选择合适的垃圾回收器(例如,G1、CMS、ZGC)。
- 调整垃圾回收器参数(例如,
-XX:MaxGCPauseMillis
、-XX:CMSInitiatingOccupancyFraction
)。 - 优化代码,减少对象创建。
- 使用并发垃圾回收器。
- 原因:
总结:
分析 GC 日志是 JVM 调优的重要环节。通过分析 GC 日志,可以了解 JVM 的内存使用情况、GC 的频率、停顿时间、回收效率等,从而找出潜在的性能问题,并进行相应的优化。