一次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排查内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Springboot集成mqtt客户端详解

    Springboot集成mqtt客户端详解

    MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。本文为大家分享了Springboot整合mqtt服务的示例代码,需要的可以参考一下
    2022-10-10
  • Java去除字符串中的空格实现方式

    Java去除字符串中的空格实现方式

    这篇文章主要介绍了Java去除字符串中的空格实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • Java Filter 过滤器详细介绍及实例代码

    Java Filter 过滤器详细介绍及实例代码

    Filter也称之为过滤器,它是Servlet技术中最实用的技术,本文章WEB开发人员通过Filter技术,对web服务器管理的所有web资源进行拦截,从而实现一些特殊的功能,本文章将向大家介绍Java 中的 Filter 过滤器,需要的朋友可以参考一下
    2016-12-12
  • 如何在springboot中引入参数校验

    如何在springboot中引入参数校验

    一般我们判断前端传过来的参数,需要对某些值进行判断,是否满足条件,而springboot相关的参数校验注解,可以解决我们这个问题,本文给大家介绍如何在springboot中引入参数校验,感兴趣的朋友一起看看吧
    2023-12-12
  • SpringBoot项目打包运行jar包的实现示例

    SpringBoot项目打包运行jar包的实现示例

    本文主要介绍了SpringBoot项目打包运行jar包的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • Java在PowerPoint中添加上标和下标的实现方法

    Java在PowerPoint中添加上标和下标的实现方法

    当我们在演示文稿中添加商标、版权或其他符号时,我们可能希望该符号出现在某个文本的上方或下方。在Microsoft PowerPoint中,我们可以通过对符号应用上标或下标格式来实现这种效果,这篇文章主要介绍了Java在PowerPoint中添加上标和下标,需要的朋友可以参考下
    2022-10-10
  • 详解java整合solr5.0之solrj的使用

    详解java整合solr5.0之solrj的使用

    本篇文章主要介绍了详解java整合solr5.0之solrj的使用 ,具有一定的参考价值,有兴趣的可以了解下
    2017-06-06
  • 浅谈Java生成唯一标识码的三种方式

    浅谈Java生成唯一标识码的三种方式

    我们经常会遇到这样的场景,需要生成一个唯一的序列号来表明某一个数据的唯一性,本文主要介绍了浅谈Java生成唯一标识码的三种方式,感兴趣的可以来了解一下
    2022-01-01
  • idea如何实现引入外部jar包

    idea如何实现引入外部jar包

    文章介绍了如何在idea软件中添加自定义jar依赖:打开idea,进入项目依赖,选择模块,点击+号选择JARs of directories,找到并选择jar文件,勾选并应用更改,最后在项目依赖中确认jar文件已成功引入
    2025-11-11
  • java:无法访问org.springframework.boot.SpringApplication的解决方法

    java:无法访问org.springframework.boot.SpringApplication的解决方法

    这篇文章主要给大家介绍了关于java:无法访问org.springframework.boot.SpringApplication的解决方法,文中通过实例代码将解决的办法介绍的非常详细,需要的朋友可以参考下
    2023-01-01

最新评论