线上服务凌晨OOM:一次因「无超时设置」引发的内存雪崩复盘

一、事故现场

凌晨2点,运维系统的告警打破了平静。

�� 【严重告警】服务器内存使用率:83%

�� 【严重告警】服务器内存使用率:97%

�� 【严重告警】服务器内存使用率:100%

从83%到100%,只用了不到10分钟。紧接着内存直接「爆表」------监控显示使用率飙到了500%+。

重启服务后,内存瞬间降了下来。但仅仅过了 3分钟,一切卷土重来。

这台机器上运行着多个组件(应用服务、Nginx、监控Agent等),贸然重启可能导致其他服务受影响。

二、排查思路:先止血,再全面排查

这次OOM和平时遇到的JVM堆内存泄漏不太一样------内存涨得太快,几乎没有渐变过程。

排查原则:先止血,再全面排查,逐项排除,最后聚焦。

2.1 第一步:止血操作

① 从负载均衡摘除问题实例

先让新流量不再进来,保护现场:

在负载均衡控制台/API上将该实例权重设为0

或者修改Nginx upstream配置,注释掉该实例

② 确认问题进程

ps aux | grep java | grep -v grep

root 22753 0.0 0.0 12345678 123456 ? Ssl 02:00 0:00 java -jar app-service.jar

PID:22753,这就是问题进程。

2.2 第二步:系统级全面排查

先确认不是机器本身的问题。

① 磁盘IO是否异常?

iostat -x 1 5

关注:%util(如果接近100%说明磁盘在疯狂读写)、await(IO等待时间是否异常)

实际结果:磁盘IO正常。

② 磁盘空间是否不足?

df -h

实际结果:各分区使用率正常。

③ 网络是否正常?

查看网络连接状态

netstat -an | awk '/^tcp/ {S[$NF]++} END {for(a in S) print a, S[a]}'

查看是否有大量TIME_WAIT

netstat -an | grep TIME_WAIT | wc -l

实际结果:网络连接状态正常,无大量异常连接。

④ MQ是否正常?

以RabbitMQ为例:检查队列堆积情况

rabbitmqctl list_queues name messages messages_ready messages_unacknowledged

实际结果:队列消息数正常,消费者连接正常。

⑤ 数据库是否正常?

检查活跃连接数

mysql -e "show status like 'Threads_connected';"

检查慢查询

mysql -e "show global status like 'Slow_queries';"

实际结果:数据库连接数正常,无异常慢查询。

⑥ Redis集群是否正常?

检查Redis连接数和内存

redis-cli info clients

redis-cli info memory

实际结果:Redis使用正常。

⑦ 当天是否有发版?

查看服务器上的应用启动时间

ps -eo pid,lstart,cmd | grep java

实际结果:当天有发版记录。

2.3 第三步:JVM进程级排查

系统层面没有问题,开始排查JVM进程。

① 查看GC状态

jstat -gcutil 22753 1000

关键指标解读:

|--------------|------------|-------------|----------------|
| 指标 | 含义 | 当前值 | 判断 |
| S0/S1 | Survivor区 | 0% | 正常 |
| E | Eden区 | 100% | Eden区满了 |
| O(Old区) | 老年代 | 93.92% | ⚠️ 老年代几乎满了 |
| M(Metaspace) | 元空间 | 89.39% | ⚠️ 元空间接近上限 |
| FGC | Full GC次数 | 10 | ⚠️ Full GC持续触发 |
| FGCT | Full GC总耗时 | 0.618s | 每次约0.6秒 |

现象分析:

• Old区93.92%接近爆满

• Full GC已经触发10次,但Old区使用率降不下来

• 这不是典型的内存泄漏(泄漏通常FGC次数少但内存一直涨)

• 而是 有大量对象被持续引用,GC无法回收

② 查看线程数

cat /proc/22753/status | grep -E "Threads|VmRSS"

Threads: 1287

VmRSS: 26500000 kB (约25.5GB)

|------------|-------------|-------------|------------|
| 指标 | 正常值 | 实际值 | 判断 |
| 线程数 | 50-100 | 1287 | ⚠️ 暴涨10倍以上 |
| 物理内存 | ~8-10GB | 25.5GB | ⚠️ 严重偏高 |

2.4 第四步:抽丝剥茧------定位阻塞点

① 导出线程栈,分析线程状态

jstack -l 22753 > thread_dump.log

统计各状态线程数量

grep -c "BLOCKED" thread_dump.log

输出:876

grep -c "Waiting on condition" thread_dump.log

输出:321

grep -c "RUNNABLE" thread_dump.log

输出:23

|----------------------|------------|------------|
| 线程状态 | 数量 | 判断 |
| BLOCKED | 876 | ⚠️ 大量线程被阻塞 |
| Waiting on condition | 321 | ⚠️ 大量线程在等待 |
| RUNNABLE | 23 | 正常 |

876个线程处于BLOCKED状态,这太不正常了。

② AI辅助分析线程堆栈

几千行堆栈人工看太费劲。可以借助AI工具分析:

【事故描述】

内存从83%飙升到500%+,10分钟内OOM,重启后3分钟复现

【已有的排查信息】

  • 系统层:磁盘IO、网络、MQ、数据库、Redis均正常

  • JVM层:Old区使用率93.92%,Full GC持续触发

  • 进程层:线程数1287,BLOCKED线程876个

【请分析】

  1. 最可能的3个根因方向

  2. 堆内存爆满但GC回收不掉,最可能的原因是什么?

  3. 我应该优先验证哪些指标?

AI分析结果直接指向:调用外部服务时无超时设置,导致线程阻塞堆积。

③ 用Arthas实时验证慢调用

连接Arthas

java -jar arthas-boot.jar

select 22753

抓取调用耗时

trace com.xxx.service.NetworkForwardService callVendor '#cost > 100'

如果有大量响应超过100ms的调用,基本确认是外部服务阻塞导致线程堆积。

④ 检查代码,确认根因

// 问题代码

public String callVendor(String param) {

return restTemplate.postForObject(vendorUrl, param, String.class);

}

RestTemplate默认没有设置超时!

三、根因链条

① 服务A 调用 下游厂商服务(走网络隔离转发)

② 代码中没有设置 connectTimeout 和 readTimeout

③ 下游响应慢(网络隔离转发有额外延迟)

④ 请求堆积 → 线程全部BLOCKED

⑤ 线程堆积 → VmRSS从8GB飙到25GB+

⑥ 大量对象被阻塞线程持有引用

⑦ GC无法回收 → Old区爆满 → Full GC频繁触发

⑧ 用户看到页面没反应 → 疯狂刷新重试

⑨ 更多请求堆积 → 内存雪崩 → OOM

⑩ 重启后循环复现

四、解决方案

public String callVendor(String param) {

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();

factory.setConnectTimeout(3000); // 连接超时3秒

factory.setReadTimeout(5000); // 读取超时5秒

factory.setConnectionRequestTimeout(3000); // 请求队列超时3秒

RestTemplate restTemplate = new RestTemplate(factory);

return restTemplate.postForObject(vendorUrl, param, String.class);

}

加上超时之后:

• 请求超过5秒自动断开,不会无限等待

• 线程可以快速释放

• GC能够正常回收对象

• 用户端看到的是明确的超时提示,而不是无限转圈

五、何时可以重启?

只有在以下情况才考虑重启:

|-------------|---------------|
| 条件 | 说明 |
| ✅ 从负载均衡摘除 | 不影响新用户请求 |
| ✅ 已保留排查信息 | jstack、日志等已导出 |
| ✅ 确认重启能解决问题 | 根因已定位并修复 |

重启命令:

优雅停机,等待当前请求处理完

kill -15 22753

或者强制终止(慎用,只有在服务已完全无响应时)

kill -9 22753

六、边缘OOM的全面排查清单

|--------------|--------------------------|------------------------|
| 排查方向 | 命令 | 关键指标 |
| 止血 | 从负载均衡摘除 | 保护现场 |
| 磁盘IO | iostat -x 1 5 | %util > 90% 异常 |
| 磁盘空间 | df -h | Use% > 90% 异常 |
| 网络 | netstat -an | 大量TIME_WAIT/CLOSE_WAIT |
| MQ | rabbitmqctl list_queues | 队列堆积/消费者掉线 |
| 数据库 | show processlist | 连接数暴涨/大量慢查询 |
| Redis | redis-cli info | 连接数/内存/bigkey |
| 发版记录 | ps -eo pid,lstart,cmd | 当天有变更 |
| JVM堆 | jstat -gcutil <pid> | O区/M区使用率、FGC次数 |
| 线程数 | cat /proc/<pid>/status | Threads 远超正常值 |
| 线程栈 | jstack -l <pid> | BLOCKED线程数异常 |
| 慢调用 | arthas trace | 调用耗时 > 阈值 |

七、AI辅助排查prompt模板

遇到类似问题时,这个prompt可以直接用:

【事故描述】

描述你遇到的问题现象,如:内存从83%飙升到100%,10分钟内OOM

【已有的排查信息】

  • 系统层:磁盘IO、网络、MQ、数据库、Redis均正常

  • JVM层:Old区使用率93.92%,Full GC持续触发

  • 进程层:线程数1287,BLOCKED线程876个

【请分析】

  1. 最可能的3个根因方向

  2. 堆内存爆满但GC回收不掉,最可能的原因是什么?

  3. 我应该优先验证哪些指标?

八、预防措施

|------------|---------------------------|
| 措施 | 具体行动 |
| 超时必须设 | 代码Review检查项,所有HTTP调用必须设超时 |
| 重试要有策略 | 限制重试次数和间隔,避免用户刷新导致雪崩 |
| 线程池监控 | 线程数超过阈值立即告警(如:>500) |
| 内存趋势监控 | 关注Old区使用率和FGC次数变化 |
| 灰度发布 | 新上线先小流量观察,及时回滚 |
| AI辅助排查 | 堆栈和监控数据第一时间发给AI分析方向 |

九、经验总结

这次OOM的特殊之处在于------表面看是内存问题,实际上是线程堆积;表面上要排查JVM,实际上要排查外部依赖。

如果不是按照「止血 → 系统排查 → JVM排查 → 线程分析 → 聚焦根因」的思路,很容易在JVM调优(加大堆、换GC算法)的方向上浪费时间。

建议所有调用外部服务的代码,都加上这三行:

.setConnectTimeout(3000); // 连接超时:3秒

.setReadTimeout(5000); // 读取超时:5秒

.setConnectionRequestTimeout(3000); // 请求队列超时:3秒

泡泡:洛水石

相关推荐
SamDeepThinking1 小时前
Spring Bean作用域的设计与使用
java·后端·面试
Flittly1 小时前
【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证
java·安全·spring·springboot·安全架构
Oliver_LaVine2 小时前
java项目启动报错:CreateProcess error=206, 文件名或扩展名太长
java·linux·jenkins
码农周2 小时前
告别大体积PDF!基于PDFBox的Java压缩工具
java·spring boot
devilnumber2 小时前
java中Redisson ,jedis,Lettuce和Spring Data Redis的四种深度对比和优缺点详解
java·redis·spring
摇滚侠2 小时前
Java 进阶教程,全面剖析 Java 多线程编程
java·开发语言
yaaakaaang2 小时前
十四、命令模式
java·命令模式
小锋java12343 小时前
【技术专题】Matplotlib3 Python 数据可视化 - Matplotlib3 绘制饼状图(Pie)
java
wuminyu3 小时前
专家视角看JVM_StartThread
java·linux·c语言·jvm·c++