oom本质是内存被占满且回收不了,而非单纯内存小;需据报错信息定位区域:堆溢出(对象多/泄漏)、元空间溢出(类过多)、gc开销超限(回收低效),再结合jfr分析分配热点。

Java JVM 内存溢出(OOM)不是“内存小了就加-Xmx”这么简单,关键在分清是真不够用,还是被代码悄悄锁死了。
先看报错信息,锁定溢出区域
错误提示里藏着最直接的线索:
- java.lang.OutOfMemoryError: Java heap space → 堆内存被占满,对象创建多、回收不了,或一次性加载过大(如查10万条不翻页)
- java.lang.OutOfMemoryError: Metaspace → 类太多装不下,常见于大量动态代理(Spring AOP、MyBatis)、热部署频繁、自定义类加载器未释放
- java.lang.OutOfMemoryError: GC Overhead limit exceeded → GC疯狂干活但收效甚微(98%时间GC,只腾出
-
java.lang.OutOfMemoryError: Direct buffer memory → NIO用了太多堆外内存,比如未显式调用
cleaner.clean()或ByteBuffer.clear(),DirectByteBuffer对象没被及时回收 - java.lang.OutOfMemoryError: Unable to create new native thread → 线程数超限,不是堆问题,而是系统级资源耗尽(如Linux默认线程数限制、JVM栈大小×线程数超物理内存)
快速验证是否内存泄漏
别急着 dump 全量堆,先做两件事:
- 用
jstat -gc <pid></pid>观察老年代使用率:如果每次 Full GC 后,已用内存不明显下降,且随时间单向爬升,基本可判定内存泄漏 - 用
jmap -histo:live <pid></pid>连打两次快照(间隔几分钟),对比哪些类实例数持续增长——尤其是你自己的业务类、静态集合、监听器、缓存容器 - 重点关注
static字段持有的集合(如static Map<string object></string>)、未注销的回调、内部类隐式持有外部类引用、未关闭的流/连接/Channel
精准定位泄漏源头(不用等OOM发生)
生产环境不宜等OOM再行动,推荐主动监控+轻量分析:
- 开启 JVM 参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps/,确保OOM时自动落盘 - 用
jmap -dump:format=b,file=heap.hprof <pid></pid>手动抓快照,配合 MAT(Memory Analyzer Tool)打开,点开 “Leak Suspects Report”,它会自动标出可疑集合和到 GC Roots 的强引用链 - 若怀疑是第三方 SDK 引起(如某埋点库静态注册监听器),在 MAT 中筛选该包名下的类,再看其被谁引用;也可临时加
-verbose:class观察类加载行为 - 对 NIO 场景,检查是否用了
ByteBuffer.allocateDirect()却没配-XX:MaxDirectMemorySize,或忘记调用cleaner.clean()
修复与预防要同步落地
改完代码只是开始,得让机制兜住后续风险:
- 资源类一律用 try-with-resources(InputStream、Connection、Socket 等),杜绝流/连接泄漏
- 缓存加过期策略(如 Caffeine 的 expireAfterWrite),禁用无上限的 static HashMap
- 大对象处理走流式或分页:数据库查用 Pageable、文件读用 BufferedReader + 行处理、JSON 解析用 Streaming API
- 定期压测 + JVM 监控(老年代水位、GC 频次、Metaspace 使用率),设置告警阈值(如 Metaspace > 80% 持续5分钟)
- 上线前跑一次 JFR(Java Flight Recorder)录制,回放分析内存分配热点,提前发现高频 new 的对象
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











