当线上出现OOM后,反思总结的方案

144 阅读4分钟

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方可以评论,我们一起探讨!

还记得当年线上出现OOM的时候,自己慌的一批,不知道该怎么办,是重启还是看日志...,没有自己的一套方案,针对过往的经历,以及后续在公司的复盘,我总结了以下几个步骤供大家参考借鉴,有不足之处请多多指点!

1. 确认OOM类型

查看错误日志,明确OOM发生的具体区域:

  • java.lang.OutOfMemoryError: Java heap space → 堆内存溢出。
  • java.lang.OutOfMemoryError: Metaspace → 元空间(方法区)溢出。
  • java.lang.OutOfMemoryError: Direct buffer memory → 直接内存溢出。
  • java.lang.OutOfMemoryError: Unable to create new native thread → 线程数超出系统限制。

2. 立即采取应急措施

  • 临时恢复服务
    • 若影响严重,快速重启服务,确保业务可用性。
    • 扩容实例或调整JVM参数(如增大堆内存:-Xmx)。
  • 保护案发现场(非常重要)
    • 开启自动Heap Dump:添加JVM参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump
    • 若未提前配置,使用命令手动生成Heap Dump(一般这个时间比较长):
      jmap -dump:format=b,file=/path/to/heapdump.hprof <pid>
      

3. 分析Heap Dump

使用工具(如 MAT, VisualVM, JProfiler)分析内存快照:

  1. 定位大对象
    • 查看 Dominator TreeHistogram,找到占用内存最多的对象。
  2. 检测内存泄漏
    • 检查 Leak Suspects 报告,查看对象引用链,确认是否因代码未释放资源导致。
  3. 典型案例
    • 静态集合类未清理:如全局Map缓存数据未移除。
    • 资源未关闭:数据库连接、文件流未释放。
    • 第三方库缺陷:如某些框架未正确管理内存。

4. 结合日志与监控

  • 查看GC日志(需提前开启 -Xloggc:/path/to/gc.log -XX:+PrintGCDetails):
    • 观察Full GC频率、耗时及内存回收效果。若频繁Full GC且内存回收不足,可能存在内存泄漏。
  • 监控数据
    • 通过Prometheus+Grafana或APM工具(如SkyWalking、Pinpoint等)查看内存使用趋势,确认是突发流量还是渐进式泄漏。
  • 怀疑方向: 慢sql、大对象、流量激增、线程池配置不合理(比如设置了无界队列)

5. 代码与配置检查

  • 最近变更:检查是否有新功能上线或配置调整(如缓存策略、线程池大小)。
  • 代码审查
    • 集合类使用:是否无限制添加元素(如ArrayList未分页)。
    • 资源释放try-with-resourcesfinally块中是否关闭资源。
    • 缓存策略:缓存是否设置过期时间或容量上限(如Guava Cache的maximumSize)。
    • 数据库:数据库查询未分页,数据太多直接导致OOM
  • JVM参数(可能性非常小)
    • 堆大小(-Xmx)、元空间(-XX:MaxMetaspaceSize)是否合理。
    • 直接内存(如Netty的-XX:MaxDirectMemorySize)是否受限。

6. 分场景解决方案

堆内存溢出

  • 内存泄漏:修复代码,移除无效对象引用。
  • 合理分配内存
    • 增大堆大小(-Xmx)。
    • 优化数据结构(如使用WeakHashMap替代强引用缓存)。

元空间溢出

  • 动态类加载:检查CGLib、反射等生成的类是否未回收。
  • 调整参数:增大 -XX:MaxMetaspaceSize

直接内存溢出

  • Netty或NIO:检查ByteBuffer.allocateDirect()是否未释放。
  • 调整参数:增加 -XX:MaxDirectMemorySize

线程数溢出

  • 检查线程池配置:避免创建无界线程池(如Executors.newCachedThreadPool())。
  • 系统限制:调整ulimit -u/etc/security/limits.conf

7. 验证与预防

  • 回归测试:修复后通过压测验证内存是否平稳。
  • 监控增强
    • 添加内存使用率、GC频率、线程数等监控项。
    • 设置阈值告警(如堆内存使用超过80%)。
  • 代码规范
    • 加强CR

总结

面对OOM问题,需快速止血(重启/扩容,恢复业务非常重要),深入分析(Heap Dump+日志),定位根本原因(代码缺陷/配置不合理),并通过监控与代码优化预防复发。核心在于:保留现场证据,结合工具定位问题,修复后持续监控