1. 概述

垃圾回收(Garbage Collection,简称 GC)是 Java 语言的一大亮点,它实现了自动内存管理,开发者无需手动申请或释放内存。这套机制看似“开箱即用”,但在生产环境中,GC 表现不佳时可能引发性能抖动、长时间停顿等问题。

要排查这些问题,第一步就是看 GC 日志。本文将系统梳理 Java 中如何开启 GC 日志,并将其输出到文件,方便后续分析。我们按 Java 8 及更早版本 vs Java 9+ 来分别讲解,避免踩坑。

2. Java 8 及更早版本的 GC 日志参数

在 Java 9 之前,JVM 使用一系列独立的 -XX 参数来控制 GC 日志行为。这些参数虽然简单,但组合使用时容易遗漏或误配。

2.1 ✅ -XX:+PrintGC(等价于 -verbose:gc

这是最基础的 GC 日志开关,开启后会在每次 Young GC 和 Full GC 发生时输出一行简要信息。

java -cp $CLASSPATH -XX:+PrintGC mypackage.MainClass

⚠️ 注意:只开这个参数输出信息非常有限,仅适合快速验证是否触发 GC。

2.2 ✅ -XX:+PrintGCDetails

想要更详细的 GC 信息?必须用这个参数。它会输出:

  • 各代内存使用情况(Eden、Survivor、Old 等)
  • GC 前后内存变化
  • GC 耗时
  • 使用的 GC 算法(如 Parallel、CMS、G1)
java -cp $CLASSPATH -XX:+PrintGCDetails mypackage.MainClass

✅ 提示:实际生产中基本不会只用 PrintGC,都是直接上 PrintGCDetails,简单粗暴有效。

2.3 ✅ 时间戳相关参数

没有时间信息的日志等于“没日期的监控报警”——根本没法定位问题。

Java 提供两个关键参数:

  • -XX:+PrintGCTimeStamps:打印 JVM 启动后经过的秒数(浮点数),例如 0.234: 开头
  • -XX:+PrintGCDateStamps:打印实际日期时间,如 2025-04-05T12:34:56.789+0800

推荐两者都加上,便于关联其他系统日志:

java -cp $CLASSPATH -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps mypackage.MainClass

2.4 ✅ -Xloggc:将 GC 日志写入文件

前面的日志默认输出到控制台(stdout),但在服务端应用中,必须重定向到文件。

这就是 -Xloggc 的作用:

# 输出到文件 /tmp/gc.log
java -cp $CLASSPATH -Xloggc:/tmp/gc.log mypackage.MainClass

# ❌ 注意:下面这行是错的!不能省略文件名
# java -cp $CLASSPATH -Xloggc mypackage.MainClass  # 这样不会写文件,而是写到 stdout

✅ 重要特性:

  • 自动启用 -XX:+PrintGC-XX:+PrintGCTimeStamps(但不会启用 PrintGCDetails
  • 支持动态路径,可结合变量生成唯一文件名,例如:
    -Xloggc:/var/log/myapp/gc-%p.log  # %p 表示进程 ID
    

3. Java 9 及以后版本的统一日志(Unified Logging)机制

从 Java 9 开始,Oracle 引入了统一的日志系统 -Xlog,取代了原来零散的 GC 日志参数。老参数大多仍兼容,但官方推荐使用新方式。

3.1 ✅ -Xlog 语法结构

-Xlog:tag*=level:output:decorators:output-options

各部分含义如下:

部分 说明
tag* 日志标签,如 gc, gc+heap, gc+pause 等,支持通配符
level 日志级别:debug, info, warning, error
output 输出目标:stdout, stderr, 或文件路径
decorators 装饰器(时间戳等):time, uptime, pid, tid, level
output-options 输出选项,如 filesize, filecount

3.2 常见用法示例

✅ 将所有 GC 日志输出到文件

java -cp $CLASSPATH -Xlog:gc*:debug:file=/tmp/gc.log mypackage.MainClass

✅ 同时输出到控制台和文件(调试时很有用)

java -cp $CLASSPATH \
  -Xlog:gc*:debug:stdout \
  -Xlog:gc*:debug:file=/tmp/gc.log \
  mypackage.MainClass

✅ 带详细时间戳和进程 ID,且按大小滚动

java -cp $CLASSPATH \
  -Xlog:gc*:debug:file=/tmp/gc.log:time,pid,uptime \
  -Xlog:gc*:debug:file=/tmp/gc.log:filecount=5,filesize=100m \
  mypackage.MainClass

解释:

  • time:打印本地时间
  • uptime:JVM 已运行时间
  • pid:进程 ID
  • filecount=5,filesize=100m:最多保留 5 个文件,每个最大 100MB

3.3 查看所有可用标签和选项

运行以下命令可查看当前 JVM 支持的所有日志配置项:

java -Xlog:help

或更详细:

java -Xlog:logging=debug -version

输出示例(节选):

Available log levels: trace, debug, info, warning, error
Available tags:  all, ascii, attach, barrier, bytecode, cds, ... gc, gc+age, gc+alloc, gc+arraycompaction, ...
Available decorators: time, uptime, timemillis, uptimemillis, timenanos, uptimenanos, pid, tid, level, tags

4. 总结与建议

场景 推荐参数
Java 8 生产环境 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:/path/to/gc.log
Java 11+ 生产环境 -Xlog:gc*:info:file=/path/to/gc.log:time,pid,uptime:filecount=5,filesize=100m
调试阶段 可额外加 debug 级别,甚至同时输出到 stdout

✅ 最佳实践:

  • 永远不要让 GC 日志丢失:必须写文件,不能只打 stdout
  • 带上时间戳timeuptime 二选一,建议都加
  • 合理滚动:避免单个日志过大,影响分析和磁盘
  • 命名规范:可用 %p(进程 ID)、%t(时间)等占位符生成唯一文件名

⚠️ 踩坑提醒:

  • Java 8 中 -Xloggc 不会自动启用 PrintGCDetails,很多人只配 -Xloggc 结果发现日志太简略
  • Java 9+ 的 -Xlog 是可重复的,可以多次出现,用于多目标输出
  • 不要迷信默认配置,GC 日志是性能调优的第一手资料,必须主动开启并归档

通过合理配置 GC 日志,你不仅能及时发现内存问题,还能为后续使用 GC 分析工具(如 GCViewer、GCEasy)打下基础。别等 OOM 了才想起看日志,那就晚了。


原始标题:Garbage Collection Logging to a File in Java