Linux

服务器 OOM 完整原因+排查+解决方案

·7 分钟阅读·2575 字
📋 目录

服务器 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)指标:

  1. 物理内存使用率曲线,是否瞬间飙升到100%
  2. Swap使用量是否持续上涨、Swap IO 飙升(频繁换页)
  3. 对应时段QPS、并发连接数是否突增
  4. 被杀进程的内存占用趋势(持续缓慢上涨=内存泄漏)

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举例)

  1. 开启HeapDump,OOM时自动导出堆快照:
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump
  2. 使用 MAT 工具分析dump,定位大对象、泄漏集合;
  3. 查看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:内存泄漏(长期缓慢涨内存)

  1. 代码修复:释放资源、分页查询、限制缓存大小;
  2. 临时兜底:增加定时重启进程/Pod(治标);
  3. 限制缓存容量,设置过期淘汰。

4.2. 场景2:内存配置太小、并发扛不住

  1. 容器:调高 limits.memory
  2. Java:合理调大Xmx,不超过宿主机70%内存;
  3. 拆分混合部署服务,单机只跑一类重型中间件。

4.3. 场景3:批量任务/突发流量瞬时OOM

  1. 大数据操作分页、分批次处理,禁止一次性加载全量数据;
  2. 接口增加限流、降级;
  3. 定时任务错峰执行,避开业务高峰。

4.4. 场景4:PageCache占用过高、业务缺内存

临时释放缓存:

echo 3 > /proc/sys/vm/drop_caches

优化内核参数,调整 vm.swappiness=10,减少过度使用Swap。

4.5. 场景5:Swap不足,无缓冲

  1. 扩容Swap分区/交换文件;
  2. 业务优先使用内存,避免Swap频繁换页(性能差)。

4.6. 场景6:虚拟机超分配导致宿主机抢占内存

  1. 降低虚拟机内存分配,不超宿主机总物理内存;
  2. 关闭不必要的内存balloon机制。

5. 预防手段(线上必备)

  1. 监控告警:内存使用率>85%、Swap突增、OOM事件实时告警;
  2. 容器强制配置内存limits,避免单Pod吃光整机内存;
  3. 所有缓存中间件配置内存上限+淘汰策略;
  4. 程序开启OOM自动dump,方便事后定位;
  5. 不混合部署内存消耗型服务;
  6. 大查询、批量脚本强制分页、限流。