OOM 之前的预警指标
一句话速记
OOM 不会突然发生,每次 Full GC 后 Old 区使用率是核心先行指标。需要监控的五个维度:① Old 区使用率(> 85% 持续 → 报警);② Full GC 频率(从 1 次/小时变成 1 次/分钟 → 预警);③ GC 停顿时间(> 500ms → 业务感知);④ Metaspace 使用率;⑤ DirectBuffer 使用量(NMT)。再加一个兜底:OOM 前 10 分钟的 heap histogram 自动采集。
通俗解释(5 分钟版)
OOM 不是心脏病突发,而是高血压慢慢恶化:
正常状态 Old 区 60-70%,Full GC 偶尔一次,停顿 < 200ms
↓
预警状态 Old 区 > 85%,Full GC 频率在变快
↓ ← 在这个阶段介入,你还有时间
危险状态 Old 区 > 95%,每次 Full GC 回收 < 5%,停顿 > 1s
↓
OOM 堆满了,JVM 抛 OOM
核心洞察:OOM 之前在监控图上一定能看到趋势变化。关键是你设了这些监控吗?
关键细节
五个必监控指标
1)Old 区使用率
# jstat 取数
jstat -gc <pid> | awk '{print $8/$7 * 100}' # OU/OC
# 监控阈值建议:
报警:> 85% 持续 5 分钟
严重:> 95% 持续 2 分钟 → 准备自动 dump
# Prometheus JMX Exporter 配置示例:
- pattern: 'java.lang<name=G1 Old Generation, type=MemoryPool>(.*)usage>(\w+)'
name: jvm_memory_pool_used_bytes
labels:
pool: old_gen配置自动动作:
Old > 90% 持续 3 分钟 → 自动 jstat -gc 输出到日志
Old > 95% 持续 2 分钟 → 自动 heap histogram(jmap -histo),轻量无 STW
Old > 98% → 自动 heap dump(-XX:+HeapDumpOnOutOfMemoryError 接管)
2)Full GC 频率和趋势
正常:1 次/几小时 或 1 次/天
注意:1 次/几分钟
预警:1 次/分钟 且频率在加快
危险:连续 Full GC(两次之间间隔 < 10 秒)
监控什么:
- FGC 次数增量(delta FGC per minute)
- 每次 Full GC 的停顿时间(FGCT / FGC 算平均值)
- 每次 Full GC 后 Old 区回收量(OU 下降幅度)
→ 回收量越来越小 → 泄漏加重
3)GC 停顿时间
阈值:
< 200ms 正常,用户无感
200-500ms 个别接口超时(取决于超时配置)
> 500ms 报警,高并发下业务受影响
> 1s 严重,加机器或紧急优化
# G1 关注 Mixed GC 的停顿,不只是 Full GC
# ZGC 停顿一般是 < 10ms,超过 50ms 就要查了
4)Metaspace 使用率
报警阈值:> 80% MaxMetaspaceSize
不设上限时也要监控(用 jstat MC/MU 列)
# 典型异常模式:
MU 持续增长 → 动态类加载泄漏
MU 在某个操作后跳涨 → 某次操作触发了大量类加载
5)堆外 DirectBuffer 使用量
// 应用内暴露指标(推荐)
@RestController
public class MemoryMonitor {
@GetMapping("/actuator/direct-memory")
public Map<String, Long> directMemory() {
long used = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)
.stream()
.filter(b -> "direct".equals(b.getName()))
.mapToLong(BufferPoolMXBean::getMemoryUsed)
.sum();
long max = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)
.stream()
.filter(b -> "direct".equals(b.getName()))
.mapToLong(BufferPoolMXBean::getTotalCapacity)
.sum();
return Map.of("used", used, "capacity", max);
}
}OOM 前自动取证
核心思路:OOM 发生时你可能不在现场,需要自动留证。
# JVM 参数:OOM 时自动 dump(兜底)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/%p-%t.hprof
-XX:+ExitOnOutOfMemoryError # OOM 后退出(让 k8s 重启)
# 应用层:OOM 前自动采集轻量信息
# 在 Old > 95% 时通过 arthas 或 jcmd 自动执行:
jcmd <pid> VM.native_memory summary > /data/dump/nmt_$(date +%s).txt
jmap -histo <pid> > /data/dump/histo_$(date +%s).txt # 轻量,不 STW
jstack <pid> > /data/dump/stack_$(date +%s).txt
# 注意顺序:先做轻量的(jstack, jmap -histo),最后做重的(heap dump)监控看板设计
生产环境 JVM 看板(Grafana / Prometheus):
Row 1: 内存概览
[Heap Used/Max 曲线] [Metaspace 曲线] [DirectBuffer 曲线]
Row 2: GC 行为
[GC 次数/min 柱状图] [GC 平均停顿 曲线] [GC 回收量 曲线]
Row 3: 线程
[线程数 曲线] [各状态线程数 堆叠图]
Row 4: 告警规则
Old > 85% → WARNING(飞书/钉钉)
Old > 95% → CRITICAL(电话/PagerDuty)
Full GC 频率 > 5次/10min → WARNING
Full GC 停顿 > 1s → WARNING
延伸追问
- Q:Old 区 85% 报警是不是太保守了?有些应用就是长期 90% → 取决于应用。如果长期稳定在 90%、Full GC 不频繁 → 可以调到 92% 再报警。关键是看趋势而非绝对值:稳定在 90% 没问题,从 60% 涨到 85% 还没停 → 有问题。
- Q:有了
HeapDumpOnOutOfMemoryError还需要手动监控吗? → 需要。因为:① 自动 dump 只在 OOM 那一刻触发,但你更想知道”OOM 前 10 分钟在发生什么”;② 自动 dump 文件可能很大(= 堆大小),生产磁盘会爆;③ 如果能提前预警,你可以在 OOM 前就介入,而不是等炸了再查。 - Q:k8s 环境 OOM 后 pod 直接重启,dump 文件丢了怎么办?
→ dump 路径挂一个 PVC 或者发到对象存储(OSS/S3)。
-XX:HeapDumpPath指向持久卷。也可以用-XX:+ExitOnOutOfMemoryError让 pod 重启,但重启前 dump 已经写好了。
我的记法
- 五个指标:Old 区、Full GC 频率、GC 停顿、Metaspace、DirectBuffer
- 核心先行指标:Old 区使用率 + Full GC 频率的趋势变化,不是绝对值
- 自动取证链:Old 95% → jmap -histo + jstack(轻量)→ OOM 时 heap dump(兜底)
- 一句话:「每次 Full GC 后 Old 水位不降→泄漏在加速,OOM 前你至少有几分钟到几十分钟的预警窗口」
状态
- 已背速记
- 能讲通俗版
- 能答追问