先把它打开
GC 日志默认是不开的,没日志等于没现场。JDK 9 起统一用 -Xlog(之前各种 -XX:+PrintGCDetails 已作废)。一份好用的配置长这样:
-Xlog:gc*,gc+heap=debug:file=/var/log/app/gc-%t.log:time,uptime,level,tags
:filecount=10,filesize=50M
// gc*:所有 gc 标签的 info 级
// gc+heap=debug:额外把堆变化打到 debug 级
// time,uptime,level,tags:每行带时间戳、运行多久、级别、标签
// filecount=10,filesize=50M:滚动 10 个文件各 50M逐字段解读
[2.345s][info][gc,start] GC(0) Pause Young (G1 Evacuation Pause)
[2.345s][info][gc,heap ] GC(0) Eden regions: 12->0(12)
[2.345s][info][gc,heap ] GC(0) Survivor regions: 0->2(2)
[2.345s][info][gc,heap ] GC(0) Old regions: 0->1
[2.345s][info][gc ] GC(0) Pause Young 24M->8M(64M) 5.234ms
// GC(0) 第几次回收,从 0 起算
// Pause Young 触发原因:新生代 evacuation
// Eden 12->0 前后 Eden 的 Region 数
// 24M->8M(64M) 前后已用->总分配(堆)
// 5.234ms 实际停顿——比 -XX:MaxGCPauseMillis 更重要
// User/Sys/Real 用户/系统/实际耗时(多线程并行 User 会大于 Real)三类该警惕的信号
停顿突涨:原本 20ms 的 Young GC 突然到 200ms,多半是 Survivor 装不下、晋升老年代过多,或大对象直进 Old。Full GC 出现:G1 的 Full GC 是降级,看到就要查——可能是 Metaspace 涨爆、大对象分配失败、或 Humongous 对象堆积。回收后堆没掉多少:[24M->22M(64M)] 说明这次回收几乎没腾出空间,对象普遍长寿,得查是否泄漏。
两个趁手工具
GCEasy/JDKMissionControl:上传日志得图表化报告,停顿分布、回收频率、晋升率一眼可见;async-profiler 的 alloc/lock 模式:配合 GC 日志定位"哪个方法在疯狂分配"——GC 日志告诉你什么时候炸,profiler 告诉你谁在制造炸药。监控三件套配齐:GC 频率 + 停顿 P99 + 堆使用率,三者配合才能形成闭环——只看一个都会有盲区。
生产实践三条
第一,日志必须滚动:不滚动迟早把磁盘写满,用 filecount+filesize 控制总占用。第二,告警联动堆栈:GC 停顿超阈值告警时,顺手 dump 一份堆和线程栈——事后排查时这是唯一的现场。第三,定期采样归档:每周留一份 GC 日志做趋势对比,容量退化往往不是单次事故,是几个月慢慢劣化的结果。下一篇看排查的另外四把刀:jstack、jmap、jstat、jcmd。
评论 (0)