GC日志与容器JVM调优#
一句话答案#
GC 日志是 JVM 性能排查的第一现场:看暂停时间、回收前后堆大小、晋升失败;容器环境下核心陷阱是 JVM 看到宿主机内存而非 container limit(JDK 8u191+ 修复),Xmx 必须比 container limit 小 20-30% 留给堆外。
核心要点
一、GC 日志关键字段解读
G1 GC 日志示例(JDK 11+统一日志 -Xlog:gc*):
[0.234s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
Eden: 24M(24M)->0M(24M) Survivors: 0M->4M Heap: 24M(256M)->8M(256M)
[0.234s][info][gc] GC(0) Pause Young 12.345msplaintext| 字段 | 含义 | 关注点 |
|---|---|---|
| Pause Young/Mixed/Full | GC 类型 | Full GC 频繁=大问题 |
| Eden: 24M→0M | Eden 回收效果 | 回收后不为0=存活对象多 |
| Survivors: 0M→4M | 存活对象晋升到Survivor | 持续增大=晋升压力 |
| Heap: 24M→8M(256M) | 堆使用:回收前→回收后(总堆) | 回收后持续增大=内存泄漏 |
| 12.345ms | STW 暂停时间 | >200ms 需关注 |
排查口诀:看类型→看暂停→看回收率→看趋势
二、常见 GC 问题模式
| 现象 | GC 日志特征 | 原因 | 解决 |
|---|---|---|---|
| Young GC 频繁 | 每秒多次 Pause Young | Eden 太小/分配速率高 | 调大 -Xmn 或 G1 自动调整 |
| Full GC 频繁 | 反复 Pause Full | 老年代满/晋升失败/Metaspace满 | 找泄漏/调大堆/检查Metaspace |
| GC 暂停过长 | Pause >500ms | 堆太大/碎片/Mixed GC 回收慢 | G1 设 -XX:MaxGCPauseMillis |
| 晋升失败 | to-space exhausted | Survivor 不够/老年代碎片 | 调大堆/减少大对象 |
| 内存泄漏 | 回收后堆使用持续增长 | 对象持续累积未释放 | jmap dump + MAT 分析 |
三、容器环境 JVM 调优
核心陷阱:JVM 看到宿主机内存
- JDK 8u131 前:
Runtime.getRuntime().maxMemory()返回宿主机总内存 - JDK 8u191+/JDK 10+:支持
-XX:+UseContainerSupport(默认开启) - 验证:
java -XX:+PrintFlagsFinal | grep MaxRAM
容器内 JVM 参数模板:
# 推荐:百分比方式(自适应 container limit)
-XX:MaxRAMPercentage=70.0 # 堆最大占容器内存70%
-XX:InitialRAMPercentage=50.0
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
# 或固定值方式(更精确控制)
# container limit = 2Gi 时:
-Xms1400m -Xmx1400m # 堆占70%
-XX:MaxMetaspaceSize=256m # Metaspace 上限
-XX:MaxDirectMemorySize=256m # 堆外直接内存
-XX:ReservedCodeCacheSize=128m
# 剩余 ~200MB 给线程栈+nativebash内存分配公式:
Container Limit ≥ Xmx + MaxMetaspaceSize + MaxDirectMemorySize
+ ReservedCodeCacheSize + 线程数×Xss + Native开销plaintextOOM Killer 排查:
# 查看是否被 OOM Killer 杀死
dmesg | grep -i "oom\|killed"
# 或
kubectl describe pod <name> | grep "OOMKilled"
# 常见原因:
# 1. Xmx = container limit → 堆外内存导致超限
# 2. Native 内存泄漏(Netty DirectBuffer/JNI)
# 3. 线程数过多(每线程默认1MB栈)bash四、JDK 8u191 前后对比
| 维度 | JDK 8u191 前 | JDK 8u191+ |
|---|---|---|
| 内存感知 | 宿主机总内存 | 容器 cgroup limit |
| CPU 感知 | 宿主机总核数 | 容器 CPU quota |
| 默认堆大小 | 宿主机内存/4(可能很大) | 容器 limit/4 |
| 线程池默认 | CPU核数(可能很多) | 容器分配核数 |
| 推荐做法 | 必须手动设 Xmx | 可用 MaxRAMPercentage |
面试回答(2分钟版)
GC 日志排查我关注四个维度:GC 类型(Young/Mixed/Full)、暂停时间、回收前后堆大小变化、长期趋势。最典型的问题是 Full GC 频繁,日志上表现为反复出现 Pause Full 且回收后堆使用持续增长,这通常意味着内存泄漏,需要 jmap dump 后用 MAT 分析。容器环境下最大的坑是 JVM 看到宿主机内存而非容器 limit,JDK 8u191 之前 Xmx 默认可能是宿主机内存的四分之一远超 container limit,直接被 OOM Killer 杀掉。解决方案是升级到 JDK 8u191+ 开启 UseContainerSupport,然后用 MaxRAMPercentage=70 让堆只占容器内存的 70%,剩下 30% 留给 Metaspace、DirectBuffer、线程栈和 Native 开销。一个典型陷阱是 Xmx 设成和 container limit 一样大,堆内没超但堆外内存导致总量超限被 OOM Killer,dmesg 里能看到 oom-kill 记录。
追问与易错
追问方向:
- “GC 日志怎么开启?”→ JDK 8:
-XX:+PrintGCDetails -Xloggc:/path/gc.log;JDK 11+:-Xlog:gc*:file=/path/gc.log - G1 的 MaxGCPauseMillis 设多少合适?(通常 200ms;设太小→GC 频繁回收少;设太大→暂停长)
- “Metaspace OOM 怎么排查?”→ 反射/动态代理/CGLIB 生成过多类;
-XX:MaxMetaspaceSize限制 + jstat 监控 - “怎么判断是堆内还是堆外 OOM?”→ 堆内:GC log 有 OutOfMemoryError;堆外:RSS 持续涨但堆内正常 + dmesg 有 oom-kill
易错点:
- ❌ “Xmx 设成容器 limit 就行”——必须留 20-30% 给堆外
- ❌ “GC 暂停长就调大堆”——可能是碎片问题,调大堆反而 Full GC 更慢
- ❌ “容器里 JVM 自动感知内存”——JDK 8u191 之前不会,必须确认版本
- ✅ 记住公式:Container Limit ≥ Xmx + Metaspace + DirectBuffer + CodeCache + 线程栈