1. 概述

本教程将介绍如何在 Java 应用中启用详细垃圾回收(Verbose GC)日志。我们将从什么是 Verbose GC 开始讲起,并说明它在性能调优和问题排查中的作用。

随后,我们会通过多个示例展示如何开启并配置 GC 日志输出,并重点讲解如何解读这些日志信息。

如果你对 JVM 的不同垃圾收集器还不太熟悉,可以先阅读我们的文章:Java 垃圾收集器详解

2. 简单了解 Verbose Garbage Collection

开启详细的 GC 日志记录是调优内存问题、诊断应用性能瓶颈的重要手段之一。很多经验丰富的开发者认为,为了持续监控应用健康状态,应该始终关注 JVM 的 GC 行为。

✅ GC 日志提供了每次垃圾回收事件的精确数据,包括执行时间和结果。
❌ 如果不关注 GC 日志,可能错过很多性能优化机会。

随着时间推移,分析 GC 日志可以帮助我们:

  • 更好地理解应用程序的行为模式
  • 调整堆大小、GC 算法等参数来提升性能
  • 减少 GC 频率或缩短单次停顿时间

2.1 示例程序

我们准备了一个简单的 Java 程序用于演示如何开启和查看 GC 日志:

public class Application {

    private static Map<String, String> stringContainer = new HashMap<>();

    public static void main(String[] args) {
        System.out.println("Start of program!");
        String stringWithPrefix = "stringWithPrefix";

        // 加载 300 万个 String 实例到 Map 中
        for (int i = 0; i < 3000000; i++) {
            String newString = stringWithPrefix + i;
            stringContainer.put(newString, newString);
        }
        System.out.println("MAP size: " + stringContainer.size());

        // 显式触发 GC
        System.gc();

        // 移除其中 200 万个对象
        for (int i = 0; i < 2000000; i++) {
            String newString = stringWithPrefix + i;
            stringContainer.remove(newString);
        }

        System.out.println("MAP size: " + stringContainer.size());
        System.out.println("End of program!");
    }
}

这个程序首先向 HashMap 中添加了 300 万个字符串对象,然后显式调用 System.gc(),再删除其中 200 万个对象。整个过程非常直观,便于观察 GC 行为。

接下来我们将看看如何开启 GC 日志。

3. 启用“简单”GC 日志

我们使用以下 JVM 参数运行程序以启用基本的 GC 日志输出:

-XX:+UseSerialGC -Xms1024m -Xmx1024m -verbose:gc

其中:

  • -XX:+UseSerialGC:指定使用串行垃圾收集器(最简单的 GC 实现)
  • -Xms1024m -Xmx1024m:设置初始与最大堆大小为 1GB
  • -verbose:gc:开启最基本的 GC 日志输出

⚠️ 注意:默认情况下,GC 日志会输出到标准输出(stdout),每发生一次 Young GC 或 Full GC 就输出一行。

3.1 初步理解日志输出

运行上述命令后,可以看到如下输出:

Start of program!
[GC (Allocation Failure)  279616K->146232K(1013632K), 0.3318607 secs]
[GC (Allocation Failure)  425848K->295442K(1013632K), 0.4266943 secs]
MAP size: 3000000
[Full GC (System.gc())  434341K->368279K(1013632K), 0.5420611 secs]
[GC (Allocation Failure)  647895K->368280K(1013632K), 0.0075449 secs]
MAP size: 1000000
End of program!

我们可以从中看到:

✅ 共发生了 4 次 GC(1 次 Full GC + 3 次 Young GC)
✅ 每一行都包含内存变化和耗时信息

3.2 详细解析日志内容

每一行 GC 日志的结构如下:

  1. GCFull GC – 垃圾回收类型
  2. (Allocation Failure)(System.gc()) – 触发原因
  3. 279616K->146232K – 回收前后占用内存
  4. (1013632K) – 当前堆总容量
  5. 0.3318607 secs – 执行耗时(秒)

例如第一行:

[GC (Allocation Failure)  279616K->146232K(1013632K), 0.3318607 secs]

表示 Eden 区已满导致 GC,回收前占用 279MB,回收后减少至 146MB,总共耗时约 0.33 秒。

虽然这种日志格式足够清晰,但缺少一些关键信息,比如:

❌ 年轻代与老年代分别回收了多少
❌ 对象晋升情况
❌ 各区域的详细使用情况

因此,更详细的日志格式更有价值。

4. 启用“详细”GC 日志

要获取更详细的 GC 日志,可以加入 -XX:+PrintGCDetails 参数:

-XX:+UseSerialGC -Xms1024m -Xmx1024m -verbose:gc -XX:+PrintGCDetails

这会提供如下额外信息:

✅ 年轻代和老年代的内存使用情况
✅ 对象晋升数量
✅ GC 时间细分(用户态、系统态、真实耗时)
✅ 堆内存整体结构快照

⚠️ 注意:自 Java 9 起,-XX:+PrintGCDetails 已被弃用,取而代之的是新的统一日志机制。新版本中应使用 -Xlog:gc*

5. 解读“详细”GC 日志

再次运行程序,输出更加丰富:

Start of program!
[GC (Allocation Failure) [DefNew: 279616K->34944K(314560K), 0.3626923 secs] 279616K->146232K(1013632K), 0.3627492 secs] [Times: user=0.33 sys=0.03, real=0.36 secs] 
[GC (Allocation Failure) [DefNew: 314560K->34943K(314560K), 0.4589079 secs] 425848K->295442K(1013632K), 0.4589526 secs] [Times: user=0.41 sys=0.05, real=0.46 secs] 
MAP size: 3000000
[Full GC (System.gc()) [Tenured: 260498K->368281K(699072K), 0.5580183 secs] 434341K->368281K(1013632K), [Metaspace: 2624K->2624K(1056768K)], 0.5580738 secs] [Times: user=0.50 sys=0.06, real=0.56 secs] 
[GC (Allocation Failure) [DefNew: 279616K->0K(314560K), 0.0076722 secs] 647897K->368281K(1013632K), 0.0077169 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
MAP size: 1000000
End of program!
Heap
 def new generation   total 314560K, used 100261K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
  eden space 279616K,  35% used [0x00000000c0000000, 0x00000000c61e9370, 0x00000000d1110000)
  from space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330188, 0x00000000d5550000)
  to   space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
 tenured generation   total 699072K, used 368281K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
   the space 699072K,  52% used [0x00000000d5550000, 0x00000000ebcf65e0, 0x00000000ebcf6600, 0x0000000100000000)
 Metaspace       used 2637K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 283K, capacity 386K, committed 512K, reserved 1048576K

5.1 分析 Minor GC 日志

以一行 Minor GC 为例:

[GC (Allocation Failure) [DefNew: 279616K->34944K(314560K), 0.3626923 secs] 279616K->146232K(1013632K), 0.3627492 secs] [Times: user=0.33 sys=0.03, real=0.36 secs]

各部分含义如下:

  • DefNew:年轻代使用的垃圾收集器(Serial GC 的年轻代实现)
  • 279616K->34944K(314560K):年轻代回收前后使用量及总容量
  • 0.3626923 secs:GC 耗时
  • [Times: ...]:CPU 使用时间统计(user/sys/real)

⚠️ 使用串行 GC 时,realuser + sys

5.2 分析 Full GC 日志

Full GC 行如:

[Full GC (System.gc()) [Tenured: 260498K->368281K(699072K), 0.5580183 secs] 434341K->368281K(1013632K), [Metaspace: 2624K->2624K(1056768K)], 0.5580738 secs] [Times: user=0.50 sys=0.06, real=0.56 secs]
  • Tenured:老年代使用的收集器
  • Metaspace:元空间信息(Java 8+)

5.3 堆内存结构分析

最后是堆内存结构摘要:

Heap
 def new generation   total 314560K, used 100261K
  eden space 279616K,  35% used
  from space 34944K,   0% used
 tenured generation   total 699072K, used 368281K
 Metaspace       used 2637K, capacity 4486K

✅ Eden 使用率 35%,老年代使用率 52%
✅ 可用于评估是否需要调整堆分区比例

6. 添加时间和日期信息

为了便于与其他日志进行关联,可以在 GC 日志中加入时间戳:

-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps

输出示例:

2018-12-11T02:55:23.518+0100: 2.601: [GC (Allocation ...

⚠️ Java 9+ 已弃用这两个参数,替代方案为:

-Xlog:gc*::time

7. 将日志写入文件

默认 GC 日志输出到 stdout,实际使用中建议将其重定向到文件:

-Xloggc:/path/to/file/gc.log

⚠️ Java 9+ 弃用该参数,改用:

-Xlog:gc:/path/to/file/gc.log

8. Java 9+ 统一日志系统

Java 9 引入了 -Xlog 作为统一的日志机制,取代了大量旧的 GC 参数。

常用示例:

-Xlog:gc                    # 输出 GC 信息到 stdout
-Xlog:gc=debug              # 输出 debug 级别日志
-Xlog:gc=debug:file=gc.txt  # 写入文件
-Xlog:gc=debug::pid,time    # 增加 PID 和时间戳

更多信息可参考 JEP 158

9. GC 日志分析工具推荐

手动分析 GC 日志效率低且容易出错。推荐使用专业工具,例如:

GCEasy.io:免费在线 GC 日志分析工具,支持多种 JVM 版本和 GC 类型,提供性能建议和图表展示。

10. 总结

本文详细介绍了 Java 中开启和分析 GC 日志的方法:

  • 如何通过 -verbose:gc-XX:+PrintGCDetails 获取详细日志
  • 如何添加时间戳、将日志输出到文件
  • Java 9+ 新的日志机制 -Xlog
  • 推荐使用专业工具进行日志分析

掌握 GC 日志的阅读与分析能力,是优化 Java 应用性能的关键技能之一。


原始标题:Verbose Garbage Collection in Java | Baeldung