30人参与 • 2026-04-15 • 其他编程

近期 oom 故障频发,一周内发生了 3 次。
但每次pod重启后,应用又一切正常;上个版本有代码发布的同学,排查了一遍新增代码,没找到可疑之处。
主要现象如下:

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 一份数据。
生成 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 表示:
gc root: java frame: com.mysql.cj.protocol.a.nativeprotocol.sendquerypacket(nativeprotocol.java:951)
这段话意思是:
byte[] 对象是被某个线程的栈上的局部变量引用着。com.mysql.cj.protocol.a.nativeprotocol 类的 sendquerypacket 方法(第 951 行)中。找到一个具体的进入看下:

可以定位到某一个 sql,并将这个 sql 展示出来。
select amount, currency from risk_message
所有的线索都指向这个 sql 查询带出来大量的数据。
通过 sql 可以缩小范围,所有涉及这个表的代码,主要是 2 个接口:
代码如下:
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();这个功能主要汇总金额,但币种不同,得按照汇率换算出来,他这块实现步骤:
问题就在查询这块,没兜住,直接查询出 百万条 记录,导致内存在接下来的 30分钟 逐渐被占满。
直接让 ai 帮我排查这 2 个接口是否有问题:
claude-4-sonnet 回答道:

ai 的回答,居然是没有问题;当再次指出问题时,ai 又站起来了。
排查过程中发现的一些事:
最后解决这个问题也比较简单:
常见 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 gc | gc 日志显示 concurrent mode failure 或 to-space exhausted | 查看 gc 日志的 concurrent mode failure 或 to-space exhausted |
到此这篇关于一次oom排查解决过程的文章就介绍到这了,更多相关oom排查内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论