服务器 OOM 完整原因+排查+解决方案
核心概念 Linux OOM Killer 在内存耗尽时选择性杀掉占用最高的进程,常见原因包括内存泄漏、配置不合理、突发流量等。
1. 什么是OOM
Linux 物理内存+Swap 耗尽,内核触发 OOM Killer,选择性杀掉占用内存最高的进程保护系统不宕机,日志会出现 Out of memory: Killed process xxx。
2. 常见OOM根因分类
2.1. 业务程序内存泄漏(最常见)
- 代码未释放对象、连接池不回收、缓存无限膨胀、循环创建对象;
- Java:堆溢出、未关闭流/连接、ThreadLocal 泄漏;
- Python/Go:长生命周期全局缓存、goroutine 泄漏、大文件一次性读入内存;
- 中间件:Redis/Mysql未限制最大内存,缓存无限写入不淘汰。
2.2. 内存配置不合理,上限过小
- JVM
-Xmx设置太小,并发一高直接撑满; - MySQL/Elasticsearch/Kafka 堆内存给太大,和系统内存争抢;
- 容器场景:K8s Pod
limits.memory设得极低,流量上涨瞬间触发OOM; - Swap 分区太小或未开启,无缓冲内存兜底。
2.3. 突发流量/批量任务瞬时占用内存暴涨
- 定时任务全量同步数据、大批量导出报表一次性加载全表;
- 秒杀、爬虫、突发请求量翻倍,连接数、缓存数据瞬间拉满;
- 日志收集、备份脚本、数据迁移程序后台抢占大量内存。
2.4. 多服务内存争抢(混合部署通病)
单机同时跑 MySQL + ES + Java服务 + Redis,各自内存配置总和超过物理内存;
系统剩余内存被文件缓存占用,业务可用内存不足。
2.5. 系统内核/缓存机制问题
- 大量文件读写导致 PageCache 占满,业务进程无空闲内存;
- 内核参数
vm.swappiness过高/过低,Swap 策略不合理; - 容器共享内核,宿主机内存压力传导到Pod触发OOM。
2.6. 内存硬件/虚拟化层面
- 虚拟机超分配内存,宿主机资源不足,宿主机回收内存导致虚拟机OOM;
- 内存硬件故障、虚拟化内存 balloon 机制抢占内存。
3. 标准排查步骤(从易到难)
3.1. 步骤1:确认是否真的OOM,看系统日志
# 系统日志查找OOM杀死记录
dmesg -T | grep -i 'out of memory'
journalctl -k | grep -i oom
出现 Killed process 代表确实OOM Killer出手。
同时记录:被杀死的进程PID、占用内存大小。
3.2. 步骤2:事发前后内存、Swap监控复盘
查看监控平台(Prometheus/Grafana/Zabbix)指标:
- 物理内存使用率曲线,是否瞬间飙升到100%
- Swap使用量是否持续上涨、Swap IO 飙升(频繁换页)
- 对应时段QPS、并发连接数是否突增
- 被杀进程的内存占用趋势(持续缓慢上涨=内存泄漏)
3.3. 步骤3:实时查看内存与占用大户
# 总内存、Swap使用
free -h
# 按内存从高到低排序进程
top # 按M排序内存
# 更直观
ps aux --sort=-%mem | head -10
# 查看PageCache占用
cat /proc/meminfo
3.4. 步骤4:区分是程序泄漏还是瞬时流量
- 内存缓慢持续上涨,低流量也不降 → 内存泄漏;
- 平时正常,高峰期瞬间打满 → 配置不足/流量突增;
- 后台定时任务执行时内存暴增 → 批量任务未做分页/限流。
3.5. 步骤5:容器环境额外排查
# 查看Pod内存限制与实际占用
kubectl top pod
kubectl describe pod xxx # 搜索OOMKilled
若状态显示 OOMKilled 说明Pod内存限制不够。
3.6. 步骤6:程序内部定位(以Java举例)
- 开启HeapDump,OOM时自动导出堆快照:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump - 使用 MAT 工具分析dump,定位大对象、泄漏集合;
- 查看GC日志,频繁Full GC且内存不释放=泄漏。
Python/Go:使用py-spy、pprof抓取内存栈,定位未释放对象。
3.7. 步骤7:检查中间件内存配置
Redis:maxmemory-policy 是否配置淘汰策略;
MySQL:innodb_buffer_pool_size 是否过大;
ES:-Xms/-Xmx 是否等于宿主机大半内存。
4. 分场景解决方案
4.1. 场景1:内存泄漏(长期缓慢涨内存)
- 代码修复:释放资源、分页查询、限制缓存大小;
- 临时兜底:增加定时重启进程/Pod(治标);
- 限制缓存容量,设置过期淘汰。
4.2. 场景2:内存配置太小、并发扛不住
- 容器:调高
limits.memory; - Java:合理调大Xmx,不超过宿主机70%内存;
- 拆分混合部署服务,单机只跑一类重型中间件。
4.3. 场景3:批量任务/突发流量瞬时OOM
- 大数据操作分页、分批次处理,禁止一次性加载全量数据;
- 接口增加限流、降级;
- 定时任务错峰执行,避开业务高峰。
4.4. 场景4:PageCache占用过高、业务缺内存
临时释放缓存:
echo 3 > /proc/sys/vm/drop_caches
优化内核参数,调整 vm.swappiness=10,减少过度使用Swap。
4.5. 场景5:Swap不足,无缓冲
- 扩容Swap分区/交换文件;
- 业务优先使用内存,避免Swap频繁换页(性能差)。
4.6. 场景6:虚拟机超分配导致宿主机抢占内存
- 降低虚拟机内存分配,不超宿主机总物理内存;
- 关闭不必要的内存balloon机制。
5. 预防手段(线上必备)
- 监控告警:内存使用率>85%、Swap突增、OOM事件实时告警;
- 容器强制配置内存limits,避免单Pod吃光整机内存;
- 所有缓存中间件配置内存上限+淘汰策略;
- 程序开启OOM自动dump,方便事后定位;
- 不混合部署内存消耗型服务;
- 大查询、批量脚本强制分页、限流。