Java 内存溢出(OOM)排查实战指南:从复现到 MAT Dump 分析

文章目录

  • 一、什么是内存溢出(OOM)
  • [二、常见导致 OOM 的原因](#二、常见导致 OOM 的原因)
    • [1. 一次读取太多数据](#1. 一次读取太多数据)
    • [2. 本地缓存无限增长](#2. 本地缓存无限增长)
    • [3. 内存泄漏](#3. 内存泄漏)
    • [4. JVM 堆配置过小](#4. JVM 堆配置过小)
  • [三、通过 Demo 快速复现 OOM(含命令)](#三、通过 Demo 快速复现 OOM(含命令))
  • [四、使用 MAT 分析 Dump](#四、使用 MAT 分析 Dump)
    • [1. 打开 Dump 文件](#1. 打开 Dump 文件)
    • [2. 查看 Leak Suspects Report](#2. 查看 Leak Suspects Report)
    • [3. Histogram:定位哪些类占用最多内存](#3. Histogram:定位哪些类占用最多内存)
    • [4. 最关键:Dominator Tree](#4. 最关键:Dominator Tree)
    • [5. Path to GC Roots:确认是谁"抓住"对象](#5. Path to GC Roots:确认是谁“抓住”对象)
  • [五、修复 OOM 的常见方式](#五、修复 OOM 的常见方式)
  • 六、总结

在 Java 服务运行过程中,OutOfMemoryError(简称 OOM)是最常见也最棘手的问题之一。它可能导致接口大量超时、服务卡死或频繁重启。要定位 OOM,我们通常需要经历下面三个步骤:

  1. 复现 OOM
  2. 生成 Heap Dump
  3. 使用 MAT 分析 Dump,找到真正的内存占用源头

一、什么是内存溢出(OOM)

JVM 在需要分配内存时,如果堆、元空间或直接内存耗尽,就会抛出 OOM。常见类型包括:

  • Java heap space:堆空间不足
  • GC overhead limit exceeded:长时间 Full GC 效果仍然不好
  • Metaspace:类元数据过多
  • Direct buffer memory:堆外内存耗尽

无论哪种类型,本质都是内存资源无法满足当前分配请求


二、常见导致 OOM 的原因

最主要的几个场景:

1. 一次读取太多数据

例如一次性加载整张大表、大文件。

2. 本地缓存无限增长

如静态 ListMap 无限追加对象,没有上限。

3. 内存泄漏

对象已无业务用途,但仍被引用导致无法被 GC 回收。

常见泄漏点包括:

  • ThreadLocal 未 remove
  • 静态集合持有大量对象
  • 单例对象持有上下文数据

4. JVM 堆配置过小

堆限制远低于实际业务需求。


三、通过 Demo 快速复现 OOM(含命令)

为了快速演示 OOM,从而获取 Dump,我们可以通过一个小 Demo 服务来制造 OOM。

假设应用提供了一个接口 /cacheOrders,每次请求会往静态集合中塞入大对象。

第一步:启动服务并开启 OOM 自动 Dump

bash 复制代码
java -Xms200m -Xmx200m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/opt/data/dump \
-jar oom-1.0-SNAPSHOT.jar

参数解释:

  • -Xms200m -Xmx200m:将堆固定为 200MB,便于快速触发 OOM
  • -XX:+HeapDumpOnOutOfMemoryError:发生 OOM 时自动生成 Dump
  • -XX:HeapDumpPath:Dump 输出目录

第二步:循环请求接口造数据

bash 复制代码
while true; do
  curl "http://192.168.121.140:8080/cacheOrders?count=1000" >/dev/null 2>&1
done

这一行脚本会不断发送请求,让服务器将大量数据塞入静态缓存。

堆只有 200MB,很快会出现:

复制代码
java.lang.OutOfMemoryError: Java heap space

同时在 /opt/data/dump 下生成 .hprof 文件。

到这里,复现 OOM 的过程就完成了,接下来进入最关键的环节------Dump 分析。


四、使用 MAT 分析 Dump

Heap Dump 是定位 OOM 的关键依据。Eclipse Memory Analyzer(MAT)是最常用的分析工具。

下面是从导入 dump 到找到问题的完整步骤。


1. 打开 Dump 文件

打开 MAT → FileOpen Heap Dump

选择 .hprof 文件,MAT 会开始构建索引(大型 dump 会稍慢)。

打开后会自动显示概览页面。


2. 查看 Leak Suspects Report

在首页点击:

复制代码
Leak Suspects Report

MAT 会自动生成一份内存使用情况的总结,包括:

  • 哪些对象占用最多内存
  • 是否存在可疑的泄漏路径
  • 哪些集合膨胀严重
  • 具体的引用链关系图(有图形化展示)

通常,一个静态集合导致的 OOM 会在这里直接出现提示。


3. Histogram:定位哪些类占用最多内存

左侧点击:

复制代码
Histogram

按 "Retained Heap" 排序,你通常会看到类似结构:

复制代码
com.example.Order           500,000 instances
java.util.ArrayList         占用巨大
byte[]                      占用大量空间

Histogram 可帮助你快速发现哪些对象数量最多、体积最大。


4. 最关键:Dominator Tree

点开:

复制代码
Dominator Tree

这是定位泄漏最核心的工具。

按 "Retained Heap" 排序后,你会看到占用最多的对象及其引用链。


这说明对象被静态集合长久持有,无法回收,是最典型的 OOM 场景。


5. Path to GC Roots:确认是谁"抓住"对象

右键某个大对象 → Path To GC Roots

MAT 会展示:

  • 是哪个单例持有

  • 是哪个静态字段持有

  • 哪段缓存没有释放

结合引用链,你就可以定位到代码具体位置。

此时即可回到 IDE 里检查业务逻辑。


五、修复 OOM 的常见方式

定位到泄漏位置或增长点后,一般从以下方向修复:

  • 限制缓存大小(推荐 Caffeine)
  • 为静态集合加清理机制
  • 避免一次性加载大量数据
  • ThreadLocal 使用完后 remove
  • 调整 JVM 堆配置,给足运行空间
  • 优化数据结构和对象生命周期

修复后建议进行一次压测,确认堆使用是否稳定。


六、总结

Java OOM 排查并不复杂,只要记住以下步骤:

  1. 启动 JVM 时开启 OOM Dump

  2. 复现问题并生成 .hprof 文件

  3. 使用 MAT 分析 Dump:

    • Leak Suspects Report
    • Histogram
    • Dominator Tree(最重要)
  4. 根据引用链定位到具体代码

  5. 修复并验证效果

相关推荐
糯诺诺米团2 小时前
C++多线程打包成so给JAVA后端(Ubuntu)<1>
java·开发语言
刘宇涵492 小时前
递归Java
java
MSTcheng.2 小时前
【C++】平衡树优化实战:如何手搓一棵查找更快的 AVL 树?
开发语言·数据结构·c++·avl
superman超哥2 小时前
Rust 泛型参数的使用:零成本抽象的类型级编程
开发语言·后端·rust·零成本抽象·rust泛型参数·类型级编程
Thomas_YXQ2 小时前
Unity3D IL2CPP如何调用Burst
开发语言·unity·编辑器·游戏引擎
superman超哥2 小时前
仓颉并发调试利器:数据竞争检测的原理与实战
开发语言·仓颉编程语言·仓颉
代码不停2 小时前
Spring Boot快速入手
java·spring boot·后端
秦苒&2 小时前
【C语言】字符函数和字符串函数:字符分类函数 、字符转换函数 、 strlen 、strcpy、 strcat、strcmp的使用和模拟实现
c语言·开发语言
小白学大数据2 小时前
Python 网络爬虫:Scrapy 解析汽车之家报价与评测
开发语言·爬虫·python·scrapy