oom是内存申请失败导致崩溃,内存泄漏是对象该回收却因引用未释放而驻留;需通过错误类型、gc日志、堆快照及工具分析区分并定位静态集合、内部类引用等泄漏点,修复后验证内存稳定性。

内存溢出(OOM)和内存泄漏是面向对象开发中两类常见但性质不同的内存问题。OOM 是程序申请内存时系统无法满足,直接崩溃;内存泄漏则是对象本该被回收却因引用未释放而长期驻留,最终可能引发 OOM。排查时需区分现象、定位根源、验证修复。
看现象:快速判断是 OOM 还是内存泄漏
OOM 通常表现为 JVM 抛出 java.lang.OutOfMemoryError,并附带具体子类型:
- Java heap space:堆内存耗尽,最常见,可能由内存泄漏或单次大对象分配引起
- Metaspace:动态类加载过多(如热部署、大量反射生成类)
- Unable to create new native thread:线程数超限,常因线程池未复用或线程未正确关闭
-
Direct buffer memory:NIO 直接缓冲区未释放(如未调用
Buffer.clear()或cleaner.clean())
内存泄漏本身不报错,但会表现为:应用运行时间越长,老年代占用持续上升、Full GC 频次增加、每次 GC 后存活对象增多、堆 dump 中存在大量本该被回收的业务对象(如缓存未清理、监听器未反注册、静态集合不断 add)。
抓现场:用工具采集关键证据
线上环境优先启用 JVM 参数保留线索:
-
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps/:OOM 时自动生成堆快照(hprof) -
-XX:+PrintGCDetails -Xloggc:/path/to/gc.log:记录 GC 行为,观察晋升率、回收效率 -
-XX:+UseG1GC -XX:MaxGCPauseMillis=200(推荐 G1)便于分析区域分布
本地复现或预发环境可配合工具:
-
jstat -gc
:实时查看各代内存使用与 GC 统计 -
jmap -histo
:按实例数排序对象,快速发现异常增长类 - Eclipse MAT / VisualVM / JProfiler:分析 hprof 文件,用“Leak Suspects Report”或“Dominator Tree”定位强引用链
查代码:聚焦面向对象中高频泄漏点
面向对象特性(封装、继承、多态)本身不导致泄漏,但不当使用引用关系极易埋雷:
-
静态集合持有对象:如
private static List<user> cache = new ArrayList();</user>—— User 实例永不释放,应改用 WeakHashMap 或加过期清理 - 内部类隐式持外部类引用:非静态内部类默认持有 outer this,若将其作为监听器注册到长生命周期对象(如 Activity、Service),会导致 outer 泄漏 —— 改为静态内部类 + WeakReference 持 outer
- 资源型对象未关闭:InputStream、Connection、ThreadLocal 等,尤其在 try-with-resources 未覆盖全部分支时 —— 必须确保 finally 中 close() 或 remove()
- 事件总线/广播注册未解绑:register 后忘记 unregister,尤其在 Android Activity/Fragment 生命周期中 —— 建议在 onDestroy/onDestroyView 中配对解绑
验修复:不止于不报错,还要看内存行为回归
修复后不能只测功能,必须验证内存是否真正稳定:
- 重复执行疑似泄漏路径(如多次打开关闭页面、反复调用某服务),观察堆内存曲线是否平稳,老年代无持续爬升
- 触发几次 Full GC 后,检查关键业务对象实例数是否回落至合理基线(可用 jmap -histo 对比)
- 长期压测(如 24 小时),监控 GC 日志中的 “GC pause” 时间和频率是否收敛,避免“缓慢泄漏”被忽略
不复杂但容易忽略的是:很多 OOM 其实是内存泄漏长期积累的结果。先稳住 OOM(调大堆、降负载),再用工具深挖泄漏根因,才能治本。










