一次OOM排查解决过程(dump文件分析)

 更新时间:2026年04月15日 10:06:50   作者:格格步入  
在生产环境中内存溢出是最常见也是最棘手的问题之一,它可能表现为某个进程被OOM killer终止、应用崩溃、服务不可用,这篇文章主要介绍了一次OOM排查解决的相关资料,需要的朋友可以参考下

一、发现问题

近期 OOM 故障频发,一周内发生了 3 次。

但每次pod重启后,应用又一切正常;上个版本有代码发布的同学,排查了一遍新增代码,没找到可疑之处。

主要现象如下

  1. 群里告警:Grafana 告警,Full GC 次数5分钟内超2次
  2. 群里告警:nacos、grpc 等中间件心跳连接超时
  3. 集群中对应服务:重启次数新增

Grafana 告警
Alertname: HK-5分钟内GC次数大于2次
状态: 告警❌
内容:  HK最近10分钟内的FullGC过多,当前值: 75.5,环境:prod,实例:10.20.56.36
应用:  xxxx
时间: 2025-10-13 19:48:51
详情: 告警详情
来源: Grafana 地址

查看 Grafana 中 JVM 问题:

可为什么会导致重启?

看看得:JVM 内部 OOM → 应用假死(线程卡死或频繁 Full GC)→ 健康检查失败 → K8s 重启 Pod

Tips:另外一种 kill

OOM Killed Pod:Kubernetes 节点的 Linux 内核 OOM Killer 杀掉了 Pod 进程(可能是 JVM 进程),通常是容器的内存限制被突破。

排查此问题困难之处

既然知道是 OOM,那就找对应 OOM 生成 dump 文件,分析即可。

运维侧反馈来不及 dump 文件,就重启了,导致dump文件丢失。

最后,那我又是如何得到这个 dump 文件的?

既然运维侧不给力,那就只能靠平时多观察下这个应用的情况,一有怀疑情况就找对应运维同学。

恰好,被我抓到一次,直接坐到运维同学旁边,让其帮我上容器帮我 dump 一份数据。

二、定位问题

2.1 定位问题:dump 文件分析

生成 dump 文件:找运维同学操作,进入对应 pod 容器中,执行如下命令:

# 1、找到对应 PID:
jps -l
# 2、执行,只想 dump 活跃的对象:
jcmd 12345 GC.heap_dump -all=false /tmp/heap_20240602.hprof

分析工具:使用的是 IDEA 自带的 Profiler

直接打开对应的 dump 文件,展示如下:

可以看到 byte[]byte[][] 数据是 MySQL 里的结果数据:

选择 byte[]

图中的 GC Root: Java Frame 表示

  • 在 Java 的垃圾回收(GC)机制中,GC Root 是一组特殊的对象引用,它们作为起点,GC 会从这些对象开始遍历引用链,找出所有可达对象。可达的对象不会被回收。
  • Java Frame 指的是某个线程的栈帧(方法调用栈)中的局部变量或参数引用了这个对象。
GC Root: Java Frame: com.mysql.cj.protocol.a.NativeProtocol.sendQueryPacket(NativeProtocol.java:951)

这段话意思是:

  • 这个 byte[] 对象是被某个线程的栈上的局部变量引用着。
  • 具体是在 com.mysql.cj.protocol.a.NativeProtocol 类的 sendQueryPacket 方法(第 951 行)中。
  • 因为它在栈上被引用,所以 GC 无法回收它,直到这个方法执行结束并且栈帧被销毁。

找到一个具体的进入看下:

可以定位到某一个 SQL,并将这个 SQL 展示出来。

SELECT amount, currency FROM risk_message

所有的线索都指向这个 SQL 查询带出来大量的数据。

2.2 定位问题:具体代码

通过 SQL 可以缩小范围,所有涉及这个表的代码,主要是 2 个接口:

  1. 入账审核列表:getInboundList
  2. 汇总-入账成功金额:querySummary —— BUG 点

代码如下:

List<RiskMessage> list = riskMessageRepo.lambdaQuery().select(RiskMessage::getAmount, RiskMessage::getCurrency)
    .eq(StringUtils.isNotBlank(clientId), RiskMessage::getClientId, clientId)
    .in(CollectionUtils.isNotEmpty(currencyList), RiskMessage::getCurrency, currencyList)
    .ge(Objects.nonNull(orderStartTime), RiskMessage::getValueDate, orderStartTime)
    .le(Objects.nonNull(orderEndTime), RiskMessage::getValueDate, orderEndTime)
    .list();

这个功能主要汇总金额,但币种不同,得按照汇率换算出来,他这块实现步骤:

  1. 查询出所有符合条件的 <金额、币种> —— 问题点
  2. 在内存本地聚合
  3. 根据汇率进行换算,得出 USD 币种的金额

问题就在查询这块,没兜住,直接查询出 百万条 记录,导致内存在接下来的 30分钟 逐渐被占满。

  1. 时间范围没生效:没有强制时间范围
  2. 按币种先汇总:币种只有百来个,返回也只有百来行

直接让 AI 帮我排查这 2 个接口是否有问题

claude-4-sonnet 回答道:

AI 的回答,居然是没有问题;当再次指出问题时,AI 又站起来了。

三、小结

排查过程中发现的一些事

  1. HTTP 请求调用时间长,不一定造成 OOM,但一定是有问题的。
  2. MySQL IN 数量能调节,可以 1w+
  3. 现阶段的AI编程,不能完全相信,会绕进一些BUG中,需要人工处理。

最后解决这个问题也比较简单

  1. 强制选定范围
  2. 按照币种 SUM,返回行数最多百来行

常见 Full GC 触发原因

触发原因说明典型特征排查方法
老年代空间不足大对象直接进入老年代,或晋升失败GC 日志显示 Allocation Failure,老年代使用率接近 100%查看 GC 日志中 Old Gen 使用率,分析对象生命周期
元空间(Metaspace)不足类加载过多,动态生成类(如反射、CGLIB)GC 日志显示 Metadata GC Threshold-XX:MaxMetaspaceSize 设置过小,或类加载泄漏
显式调用 System.gc()代码或第三方库调用GC 日志显示 System.gc()GC 日志会显示 System.gc() 触发
直接内存不足DirectByteBuffer / Netty 堆外内存耗尽堆外内存不足时 JVM 会频繁 Full GC 尝试释放 Cleaner-XX:MaxDirectMemorySize,用 jcmd VM.native_memory summary 查看
大对象分配失败超过 PretenureSizeThreshold 直接进老年代GC 日志显示 Promotion failed调整阈值或优化对象分配
CMS/G1 的 remark 阶段失败并发回收失败,退化为 Full GCGC 日志显示 concurrent mode failure 或 to-space exhausted查看 GC 日志的 concurrent mode failure 或 to-space exhausted

到此这篇关于一次OOM排查解决过程的文章就介绍到这了,更多相关OOM排查内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 浅谈virtual、abstract方法和静态方法、静态变量理解

    浅谈virtual、abstract方法和静态方法、静态变量理解

    下面小编就为大家带来一篇浅谈virtual、abstract方法和静态方法、静态变量理解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • idea安装hsdis的方法

    idea安装hsdis的方法

    这篇文章主要介绍了idea安装hsdis,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • IDEA中print("")输出中文乱码的解决方式

    IDEA中print("")输出中文乱码的解决方式

    文章描述了在Java中使用System.out.print输出中文时出现乱码的问题,并提供了解决方案:修改项目的编码设置从GBK到UTF-8
    2025-10-10
  • String类的获取功能、转换功能

    String类的获取功能、转换功能

    这篇文章给大家介绍了String类的获取功能:String类的基本获取功能、获取功能的举例子、String类的基本转换功能、转换功能的举例子。具体详情大家参考下本文
    2018-04-04
  • Java BigDecimal类的一般使用、BigDecimal转double方式

    Java BigDecimal类的一般使用、BigDecimal转double方式

    这篇文章主要介绍了Java BigDecimal类的一般使用、BigDecimal转double方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • Java IO文件过滤器对命令设计模式的使用

    Java IO文件过滤器对命令设计模式的使用

    java io流里面使用到了很多的设计模式,最典型的就是装饰模式,还有命令模式,下面分两部分来讲Java IO文件过滤器对命令设计模式的使用,一起看看吧
    2017-06-06
  • Java读取其下所有文件夹与文件路径的方法

    Java读取其下所有文件夹与文件路径的方法

    这篇文章主要为大家详细介绍了Java读取其下所有文件夹与文件路径的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • Spring IOC注入的两种方式详解以及代码示例

    Spring IOC注入的两种方式详解以及代码示例

    在Spring框架中,依赖注入(Dependency Injection,DI)是通过控制反转(Inversion of Control,IOC)实现的,Spring提供了多种方式来实现IOC注入,本文就给大家介绍两种注入的方式:基于XML和基于注解,文中有详细的代码示例,需要的朋友可以参考下
    2023-08-08
  • 带你了解Spring AOP的使用详解

    带你了解Spring AOP的使用详解

    这篇文章主要介绍了Spring AOP的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • Mybatis-Plus逻辑删除的用法详解

    Mybatis-Plus逻辑删除的用法详解

    这篇文章主要为大家详细介绍了Mybatis-Plus 逻辑删除的用法,文中有详细的代码示例,对我们的学习或工作有一定的帮助,需要的朋友可以参考下
    2023-07-07

最新评论