Skip to content

怎么分析 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 后的分析流程:

  1. 前提:有 dump——-XX:+HeapDumpOnOutOfMemoryError 必配。没有 dump 的 OOM 基本等于无头案;
  2. MAT(Eclipse Memory Analyzer)打开 dump:
    • Leak Suspects 报告:自动指认嫌疑(某个类占了 70% 内存这种);
    • Dominator Tree(支配树):按"释放它能回收多少内存"排序,最大的支配者就是泄漏根;
    • Path to GC Roots:选中可疑对象看谁引用着它不放——引用链就是"案发路径"(比如 static Map → 业务对象 → 几百万条订单 DTO);
  3. 对号入座常见根因:静态缓存无上限、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 后分析四步:

  1. 确保有 dump:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=... 是标配(事后无法再取,对象已不在);
  2. MAT 打开 → Leak Suspects → 看被谁持有;
  3. 两个时间点对比:泄漏是慢性病,间隔几小时两份 histo/diff,持续增长的对象就是嫌疑人;
  4. 结合业务回溯:最近的变更、新上的缓存/功能、连接数、线程数。

经验规律:老年代锯齿基线稳步抬升 = 泄漏;瞬时打满 = 大查询/大消息;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 分析三板斧:

  1. Leak Suspects 报告:自动给疑似泄漏点;
  2. Dominator Tree:按"支配的内存大小"排序,找到占大头的对象;
  3. 引用链(Path to GC Roots):看大对象被谁引用着不放(HashMap entry、ThreadLocal、静态集合、连接池)。

区分两种"内存高":

  • 泄漏:Full GC 后老年代仍持续上涨、不回落 → 引用链找持有者;
  • 配置不足/大对象:GC 后正常回落,只是峰值高 → 调堆/改代码(分页查询、流式处理)。

我的案例节奏:先 jstat 判性质 → jmap -histo 找可疑类 → dump + MAT 引用链锁定持有者 → 代码修复。全流程 30 分钟内能闭环,关键是别乱试,按证据走。