OOM无法捕获且会导致JVM崩溃,定位需坚持“早发现、留现场、看堆镜像、查对象来源”:先确认类型(如heap space、Metaspace等),再通过-XX:+HeapDumpOnOutOfMemoryError自动保存hprof,最后用MAT分析Dominator Tree和Leak Suspects定位内存大户及泄漏根因。

Java内存溢出(OutOfMemoryError,简称OOM)不是普通异常,无法用try-catch捕获,一旦发生,JVM通常会直接崩溃或进入不可用状态。定位OOM的关键在于:**早发现、留现场、看堆镜像、查对象来源**。
一、确认OOM类型,缩小排查范围
OOM有多种表现,不同原因对应不同处理路径:
- java.lang.OutOfMemoryError: Java heap space —— 堆内存不足,最常见,重点查对象分配和泄漏
- java.lang.OutOfMemoryError: Metaspace —— 元空间耗尽,多见于频繁动态生成类(如Spring Boot热部署、大量反射、Groovy脚本)
- java.lang.OutOfMemoryError: GC overhead limit exceeded —— GC花太多时间却回收极少内存,说明堆里存在大量存活对象或碎片严重
- java.lang.OutOfMemoryError: unable to create new native thread —— 线程数超系统限制,检查线程创建逻辑和线程池配置
- java.lang.OutOfMemoryError: Direct buffer memory —— 直接内存(-XX:MaxDirectMemorySize)不够,常见于NIO、Netty等场景
二、启动时加参数,自动保留“案发现场”
不要等OOM发生后再手动dump,容易错过时机。建议在JVM启动参数中固定加入:
- -XX:+HeapDumpOnOutOfMemoryError —— OOM时自动生成堆转储文件(hprof)
- -XX:HeapDumpPath=/path/to/dumps/ —— 指定保存路径,确保目录可写且空间充足
- -XX:+PrintGCDetails -Xloggc:gc.log(Java 8)或 -Xlog:gc*:gc.log(Java 9+)—— 记录GC行为,辅助判断是否长期回收失败
示例完整参数:
-Xms2g -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/app/dumps/ -Xloggc:/opt/app/logs/gc.log -XX:+PrintGCDetails
三、分析堆转储文件(hprof),找“大户”对象
用工具打开hprof文件(如Eclipse MAT、JProfiler、VisualVM),重点关注:
立即学习“Java免费学习笔记(深入)”;
- Dominator Tree(支配树) —— 查看谁占用了最多堆内存,按“Retained Heap”排序,一眼识别内存大户
- Leak Suspects Report(泄漏嫌疑报告) —— MAT自动生成,常能直接指出可疑的静态集合、未关闭的资源、线程局部变量等
- Path to GC Roots —— 对某个大对象右键 → “Show Paths to GC Roots”,查看它为何没被回收(比如被静态Map强引用、被ThreadLocal持有)
常见泄漏模式举例:
• 静态HashMap缓存未设上限且不清理
• 使用ThreadLocal后未调用remove(),导致线程复用时对象累积
• 数据库连接、文件流、Netty ByteBuf等资源未显式释放
四、结合代码与运行时监控,验证和预防
光看dump不够,需联动验证:
- 用jstat -gc
实时观察GC频率、堆使用率变化趋势 - 用jmap -histo
快速查看当前类实例数量,定位是否某类对象暴增 - 用jstack
查线程状态,确认是否存在大量WAITING线程堆积(可能引发内存缓慢增长) - 在关键缓存、监听器注册、资源申请处添加日志或指标埋点,便于事后追溯
预防建议:
✓ 优先用ConcurrentHashMap代替静态HashMap做缓存,并配合LRU或过期策略
✓ 所有实现AutoCloseable的资源,务必用try-with-resources包裹
✓ ThreadLocal变量声明为static final,使用后及时remove()
✓ 生产环境避免无限制的递归、深度JSON反序列化、大文件一次性读入内存
基本上就这些。OOM定位不复杂但容易忽略细节,关键是把dump机制配好、学会看支配树、养成资源管理习惯。










