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
:进程 IDfilecount=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
- 带上时间戳:
time
或uptime
二选一,建议都加 - 合理滚动:避免单个日志过大,影响分析和磁盘
- 命名规范:可用
%p
(进程 ID)、%t
(时间)等占位符生成唯一文件名
⚠️ 踩坑提醒:
- Java 8 中
-Xloggc
不会自动启用PrintGCDetails
,很多人只配-Xloggc
结果发现日志太简略 - Java 9+ 的
-Xlog
是可重复的,可以多次出现,用于多目标输出 - 不要迷信默认配置,GC 日志是性能调优的第一手资料,必须主动开启并归档
通过合理配置 GC 日志,你不仅能及时发现内存问题,还能为后续使用 GC 分析工具(如 GCViewer、GCEasy)打下基础。别等 OOM 了才想起看日志,那就晚了。