P02 线上 OOM 怎么定位和解决
面试题:线上服务突然 OOM 了,怎么快速定位原因并解决?
处理原则
线上优先恢复服务(重启/扩容),同时保留现场用于事后分析;绝不能重启完就完事,必须找到根因。
视频给出的 OOM 三大原因
- 一次性申请的对象太多:如全量查询把千万级数据全塞进 List → 解决:分页/分批;
- 内存资源耗尽未释放:如不断创建线程、JDBC Connection 不关闭 → 解决:用完即释放 + 池化(限制最多申请 N 个资源);
- 分配的内存本身不够:应用日常操作就需要较大堆(有大对象)→ 解决:调大堆内存。
第一步:先保命
- 立即重启或摘流量,恢复可用性;
- 如果是内存泄漏,重启只能缓解,问题会复现,必须分析堆。
第二步:保留现场(关键)
启动参数里提前加上自动 dump:
bash
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heap.hprofOOM 时 JVM 自动生成堆转储文件。如果没提前配,用 jmap 手动抓:
bash
jmap -dump:format=b,file=heap.hprof <pid>注意:手动 dump 会让服务停顿(STW),大堆慎用,通常先做一次轻量观察(jstat)再决定。
视频强调:建议"无脑"加上 HeapDumpOnOutOfMemoryError,否则系统挂掉后无据可查;但要保证磁盘空间足够(dump 文件可能非常大)。
第二步半:先用 jmap -heap 看堆分配
系统还没挂时,先看当前堆配置与各区使用量:
bash
jmap -heap <pid>会输出:最大堆、新生代(Eden/S0/S1)、老年代各用了多少、空闲多少,结合业务判断是"不够用"还是"异常占用"。
第三步:分析堆转储
用 MAT(Eclipse MAT)或 VisualVM 打开 heap.hprof:
- 看 Leak Suspects(泄漏嫌疑),MAT 会给出"大对象 + GC Roots 引用链";
- 看 Dominator Tree / 支配树,找出占用内存最大的对象;
- 看是否有一个集合/数组/Map 无限增长,被什么静态对象或容器长期持有。
第四步:对照代码找根因
常见 OOM 根因:
| 原因 | 表现 | 对策 |
|---|---|---|
| 大对象/一次性全量查询 | 集合里有超大 List | 分页、流式读取 |
| 集合无限增长 | Map/List 只加不减 | 及时清理、限容量、弱引用 |
| ThreadLocal 未清理 | 线程池复用导致泄漏 | try-finally remove |
| 缓存无限增长 | 缓存撑爆堆 | 设置容量/过期/LRU |
| 连接/流未关闭 | 资源泄漏 | try-with-resources |
| 元空间/直接内存 | Metaspace / DirectBuffer OOM | 检查类加载器泄漏、堆外内存 |
加分点
- OOM 不止堆 OOM:
java.lang.OutOfMemoryError: Java heap space、Metaspace、Direct buffer memory、GC overhead limit exceeded要能区分; - 观察指标:
jstat -gcutil <pid> 1000看 GC 频率;top -H -p <pid>看线程 CPU,配合jstack找谁在疯狂分配; - 如果是请求流量突增导致的 OOM(非泄漏),重点查限流、缓存、并发数;
- 大促/高并发场景建议提前做压测,发现内存水位异常。
一句话总结
先重启保服务,用 HeapDumpOnOutOfMemoryError/jmap 保留现场,MAT 分析 GC Roots 引用链找到泄漏源头,再针对性修复;治本是优化代码,防再发靠监控(堆水位、GC、dump 自动归档)。