必须同时配置-Xloggc和-XX:+PrintGCDetails等打印参数才能生成有效GC日志;-XX:+UseGCLogFileRotation需配合NumberOfGCLogFiles≥2和GCLogFileSize使用才触发轮换;G1下启用字符串去重需额外添加-XX:+PrintStringDeduplicationStatistics;Docker中须确保日志目录存在且进程有写权限。

直接加 -Xloggc 不等于有日志输出
很多同学加上 -Xloggc:/path/to/gc.log 就以为 GC 日志会自动出现,结果启动后文件为空或根本没生成。这是因为 OpenJDK 8 中 -Xloggc 默认只启用日志写入开关,但不自动开启日志内容——你还得配 -XX:+PrintGCDetails(或至少 -XX:+PrintGC)。否则 JVM 会静默跳过所有 GC 事件记录。
常见错误现象:
-
gc.log文件存在但大小为 0 - 日志里只有头信息(如 VM 版本),没有 GC 暂停行(如
[GC pause (G1 Evacuation Pause) (young)])
必须组合使用:
java -Xloggc:/data/logs/gc.log \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintHeapAtGC \
-jar app.jar
-XX:+UseGCLogFileRotation 不是“追加”,而是轮换
想让每次重启都保留上次的 GC 日志?别指望 -Xloggc 自带追加模式。OpenJDK 8 的 -Xloggc 底层用的是 fopen(filename, "w"),每次 JVM 启动都会清空重写。所谓“追加效果”,其实是靠日志轮换(rotation)模拟出来的:它不往同一个文件里追加,而是在满额或重启时生成新编号文件(如 gc.log.1、gc.log.2),并按策略丢弃最旧的。
要启用轮换,必须同时满足三个条件:
-
-XX:+UseGCLogFileRotation(开关必须显式打开) -
-XX:NumberOfGCLogFiles=N(N ≥ 2,设为 1 会退化成覆盖行为) -
-XX:GCLogFileSize=XXM(比如30M,否则不触发滚动)
示例有效配置:
java -Xloggc:/data/logs/gc.log \
-XX:+UseGCLogFileRotation \
-XX:NumberOfGCLogFiles=5 \
-XX:GCLogFileSize=30M \
-XX:+PrintGCDetails \
-jar app.jar
G1 收集器下,别漏掉 -XX:+UseStringDeduplication 相关日志
如果你用的是 G1(像你生产环境那样配了 -XX:+UseG1GC),且启用了字符串去重(-XX:+UseStringDeduplication),默认情况下这部分操作不会出现在 GC 日志里。要看到字符串去重统计(如 deduplicated 字节数、处理耗时),得额外加参数:
-
-XX:+PrintStringDeduplicationStatistics(JDK 8u20+ 支持)
否则你会在日志里看到正常的 Young GC 和 Mixed GC,但完全找不到任何关于 String 去重的线索,排查内存占用异常时容易误判。
Docker 环境里路径和权限最容易被忽略
在容器中跑 JVM,-Xloggc:/data/logs/gc.log 看似没问题,但实际常因两件事失败:
- 目标目录
/data/logs/在镜像里不存在,JVM 不会自动创建父目录,直接静默失败(文件不生成,也无报错) - Java 进程用户(如
nonroot)对/data/logs/无写权限,同样不报错,只是日志消失
建议启动前在 Dockerfile 或 entrypoint 中确保:
RUN mkdir -p /data/logs && chown -R 1001:1001 /data/logs
其中 1001 是你 Java 进程实际 UID(可通过 ps -eo uid,comm | grep java 确认)。不检查这点,日志配置再全也没用。


















