面试知识库
困难

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.345ms
plaintext
字段含义关注点
Pause Young/Mixed/FullGC 类型Full GC 频繁=大问题
Eden: 24M→0MEden 回收效果回收后不为0=存活对象多
Survivors: 0M→4M存活对象晋升到Survivor持续增大=晋升压力
Heap: 24M→8M(256M)堆使用:回收前→回收后(总堆)回收后持续增大=内存泄漏
12.345msSTW 暂停时间>200ms 需关注

排查口诀:看类型→看暂停→看回收率→看趋势

二、常见 GC 问题模式

现象GC 日志特征原因解决
Young GC 频繁每秒多次 Pause YoungEden 太小/分配速率高调大 -Xmn 或 G1 自动调整
Full GC 频繁反复 Pause Full老年代满/晋升失败/Metaspace满找泄漏/调大堆/检查Metaspace
GC 暂停过长Pause >500ms堆太大/碎片/Mixed GC 回收慢G1 设 -XX:MaxGCPauseMillis
晋升失败to-space exhaustedSurvivor 不够/老年代碎片调大堆/减少大对象
内存泄漏回收后堆使用持续增长对象持续累积未释放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 给线程栈+native
bash

内存分配公式:

Container Limit ≥ Xmx + MaxMetaspaceSize + MaxDirectMemorySize
                  + ReservedCodeCacheSize + 线程数×Xss + Native开销
plaintext

OOM 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 + 线程栈