怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析?
- 标签:后端、场景题、JVM
🏛 业务架构师
内存占用的日常分析:
- jstat -gcutil <pid> 1000:秒级看各代占用与 GC 频率——老年代持续上涨且 Full GC 后不回落 = 泄漏信号;
- jmap -histo <pid>:对象直方图,前 20 大对象类型一眼定位嫌疑(某个业务 DTO 占 40% 就有故事了);
- Arthas dashboard/dashboard + memory:生产在线诊断不重启;
- 常态化靠 Apm + Prometheus(JVM exporter):堆趋势、GC 停顿、元空间、堆外——监控前移,别等 OOM 才看。
OOM 后的分析流程:
- 前提:有 dump——
-XX:+HeapDumpOnOutOfMemoryError必配。没有 dump 的 OOM 基本等于无头案; - MAT(Eclipse Memory Analyzer)打开 dump:
- Leak Suspects 报告:自动指认嫌疑(某个类占了 70% 内存这种);
- Dominator Tree(支配树):按"释放它能回收多少内存"排序,最大的支配者就是泄漏根;
- Path to GC Roots:选中可疑对象看谁引用着它不放——引用链就是"案发路径"(比如 static Map → 业务对象 → 几百万条订单 DTO);
- 对号入座常见根因:静态缓存无上限、ThreadLocal 未 remove(线程池场景)、监听器/连接未注销、类加载器泄漏(热部署)、大查询一次性载入。
架构师的两个机制化要求:
- OOM 现场保留流程:OOM 先摘流量、dump 完成再重启——很多团队第一反应是秒重启,现场全毁,同类问题反复发生;
- 复盘产出规范:每次 OOM 复盘必须落到规范或门禁(如"本地缓存必须有上限与 TTL"进 CR checklist)——事故不沉淀制度,就是白交学费。
一句话:内存分析 = 指标看趋势 + dump 找支配树 + 引用链定责,而 dump 开关和"先留现场再重启"是制度问题,不是技术问题。
🏗 系统架构师
内存占用分析三件套:
- jstat -gcutil <pid> 1000:快速看各代占用、GC 次数与耗时趋势——判断是"泄漏"(老年代持续上涨)还是"配置不足";
- jmap -histo <pid>:对象直方图(按类聚合数量与大小),快速定位哪类对象暴涨;
jmap -dump:format=b,file=heap.hprof拉全量快照(注意大堆会 STW,优先 gdb + coprocess 或 JVM 参数 HeapDumpOnOutOfMemoryError 自动留现场); - MAT / JProfiler 分析 dump:Dominator Tree(支配树)看谁占大头、Path to GC Roots(排除弱引用)找引用链、Leak Suspects 自动报告——泄漏对象一般是被静态集合、ThreadLocal、缓存框架持有;
- 补充:arthas dashboard/heapdump 在线做;容器环境看 RSS 与堆的差值(堆外);NMT(-XX:NativeMemoryTracking)分析 JVM 自身开销。
OOM 后分析四步:
- 确保有 dump:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=...是标配(事后无法再取,对象已不在); - MAT 打开 → Leak Suspects → 看被谁持有;
- 两个时间点对比:泄漏是慢性病,间隔几小时两份 histo/diff,持续增长的对象就是嫌疑人;
- 结合业务回溯:最近的变更、新上的缓存/功能、连接数、线程数。
经验规律:老年代锯齿基线稳步抬升 = 泄漏;瞬时打满 = 大查询/大消息;Metaspace 涨 = 类生成泄漏;堆外涨 = Netty/NIO 未释放。先定型再取证,少走弯路。
💻 全栈开发专家
分"实时观测"和"事后取证"两条路。
实时观测(还活着时):
bash
# 1. GC 健康度:各代使用率、GC 次数与耗时
jstat -gcutil <pid> 1000
# 关注:O 区是否持续上涨且回收后不降(泄漏信号);FGC 次数/耗时
# 2. 对象分布直方图(轻量,优先)
jmap -histo <pid> | head -30 # 按类看实例数和字节数
# 3. 线上诊断利器 Arthas
dashboard # 总览
heapdump /tmp/heap.hprof # 需要时再 dump事后取证(OOM / 精确分析):
bash
# 触发 dump(注意:8G 堆 dump 会 STW 数秒,低峰做)
jmap -dump:live,format=b,file=/tmp/heap.hprof <pid>
# 生产军规:启动参数预配,OOM 自动 dump
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump/MAT 分析三板斧:
- Leak Suspects 报告:自动给疑似泄漏点;
- Dominator Tree:按"支配的内存大小"排序,找到占大头的对象;
- 引用链(Path to GC Roots):看大对象被谁引用着不放(HashMap entry、ThreadLocal、静态集合、连接池)。
区分两种"内存高":
- 泄漏:Full GC 后老年代仍持续上涨、不回落 → 引用链找持有者;
- 配置不足/大对象:GC 后正常回落,只是峰值高 → 调堆/改代码(分页查询、流式处理)。
我的案例节奏:先 jstat 判性质 → jmap -histo 找可疑类 → dump + MAT 引用链锁定持有者 → 代码修复。全流程 30 分钟内能闭环,关键是别乱试,按证据走。