Skip to content

P02 线上 OOM 怎么定位和解决 ​

面试题:线上服务突然 OOM 了,怎么快速定位原因并解决?

处理原则 ​

线上优先恢复服务(重启/扩容),同时保留现场用于事后分析;绝不能重启完就完事,必须找到根因。

视频给出的 OOM 三大原因 ​

  1. 一次性申请的对象太多:如全量查询把千万级数据全塞进 List → 解决:分页/分批;
  2. 内存资源耗尽未释放:如不断创建线程、JDBC Connection 不关闭 → 解决:用完即释放 + 池化(限制最多申请 N 个资源);
  3. 分配的内存本身不够:应用日常操作就需要较大堆(有大对象)→ 解决:调大堆内存。

第一步:先保命 ​

  • 立即重启或摘流量,恢复可用性;
  • 如果是内存泄漏,重启只能缓解,问题会复现,必须分析堆。

第二步:保留现场(关键) ​

启动参数里提前加上自动 dump:

bash
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heap.hprof

OOM 时 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 自动归档)。

基于 VitePress 重建