文章背景图

Linux 服务器频繁 OOM:从现场证据、内存泄漏到容量治理

2026-08-08
10
- 字
- 分钟

OOM(Out Of Memory)不是一句“内存不够”就能解释的故障。它可能发生在整机、容器 cgroup、systemd 单元或应用自身堆内存中;同一台机器甚至可能还有大量可用内存,但某个受限容器已经被杀。本文提供从证据保全、范围判断、泄漏定位到容量治理的完整流程。

一、先确认是哪一层 OOM

  • 主机 OOM:内核在全局内存紧张时选择进程杀死。

  • cgroup/容器 OOM:进程触碰 MemoryMax 或容器 limit。

  • 应用 OOM:JVM、Node.js、Python 等运行时达到自身堆限制。

  • 内存分配失败:overcommit、地址空间、ulimit 或碎片导致申请失败。

二、第一时间保留现场

date -Is
uptime
free -h
vmstat 1 5
ps -eo pid,ppid,user,%mem,rss,vsz,stat,etime,cmd --sort=-rss | head -40
journalctl -k --since "2 hours ago" | grep -Ei 'oom|out of memory|killed process'
dmesg -T | grep -Ei 'oom|out of memory|killed process' | tail -80
cat /proc/meminfo

保存完整日志,不要只截取 “Killed process” 一行。内核日志中的 constraint、task_memcg、total-vm、anon-rss、file-rss、shmem-rss 和 oom_score_adj 能帮助还原选择过程。

三、先止血,避免扩大影响

  1. 暂停非关键批任务、导出、压缩和大查询。

  2. 摘除异常实例,确认健康节点有足够容量后再切流。

  3. 按预案扩容或临时提高受限容器额度,同时记录原值和回滚条件。

  4. 回滚刚发布的版本或关闭引发内存增长的新功能。

  5. 只有确认业务可中断且证据已保存后才重启。

不要把“增加 swap”当永久修复;它可能降低被杀概率,却让延迟显著恶化。

四、判断内存到底去了哪里

free -h
cat /proc/meminfo | grep -E 'MemAvailable|AnonPages|Cached|Slab|SReclaimable|SUnreclaim|Shmem|PageTables'
slabtop -o
smem -tk 2>/dev/null || true
ps -eo pid,user,rss,vsz,%mem,cmd --sort=-rss | head -30

重点看 MemAvailable,而不是只看 free。匿名页高通常来自应用堆;page cache 可回收但并非“无用”;SUnreclaim 持续增长可能是内核对象或驱动问题;共享内存需检查 /dev/shm、tmpfs 和 IPC。

五、检查进程增长趋势

pidstat -r 5 12
cat /proc/<PID>/status | grep -E 'VmRSS|VmSize|RssAnon|RssFile|RssShmem|Threads'
cat /proc/<PID>/smaps_rollup
pmap -x <PID> | tail -20
ls -l /proc/<PID>/fd | wc -l

单点快照不能证明泄漏。需要把 RSS、匿名页、堆对象、线程数、连接数和业务流量放在同一时间轴上。流量下降后内存仍单调增长,且 GC 后基线不回落,才更像泄漏。

六、容器与 Kubernetes 排查

docker inspect <CONTAINER> --format '{{.State.OOMKilled}} {{.HostConfig.Memory}}'
docker stats --no-stream
kubectl describe pod <POD> -n <NS>
kubectl top pod <POD> -n <NS> --containers
kubectl get events -n <NS> --sort-by=.lastTimestamp | tail -30
kubectl get pod <POD> -n <NS> -o jsonpath='{.status.containerStatuses[*].lastState}'

看到 OOMKilled 后同时核对 requests、limits、节点内存压力和重启次数。limit 过低会误杀,过高则可能让节点发生全局 OOM;requests 用于调度,limit 才是硬上限。不要只提高 limit 而忽略泄漏和副本容量。

七、systemd 与 cgroup v2

systemctl show <SERVICE> -p MemoryCurrent -p MemoryMax -p OOMPolicy
systemd-cgtop
cat /sys/fs/cgroup/<PATH>/memory.current
cat /sys/fs/cgroup/<PATH>/memory.max
cat /sys/fs/cgroup/<PATH>/memory.events

memory.events 中 oom、oom_kill 的增长可以证明 cgroup 范围内发生 OOM。修改 MemoryMax 前先确认宿主机余量,并把配置写入受控配置而不是只做临时命令。

八、应用层泄漏定位

Java:开启 HeapDumpOnOutOfMemoryError 并确保磁盘容量充足;用 jcmd GC.heap_info、GC.class_histogram 观察,必要时离线分析 heap dump。不要在高压生产进程上频繁执行全量直方图。

jcmd <PID> GC.heap_info
jcmd <PID> VM.native_memory summary
jstat -gcutil <PID> 1000 10

Node.js:核对 --max-old-space-size、heapUsed、external、arrayBuffers,并使用受控 heap snapshot。Python:通过 tracemalloc、objgraph 或应用指标比较对象增长;同时排除 native 扩展、缓存无界和 worker 数量过多。

九、容易误判的场景

  • Linux 使用空闲内存做缓存并不等于泄漏。

  • 进程 VSZ 很大不代表实际驻留内存大,应结合 RSS/PSS。

  • 应用报 OOM 不一定触发内核 OOM,运行时可能自行退出。

  • 重启后恢复只能证明释放了内存,不能证明根因已解决。

  • 缓存命中率提升带来的可控增长与无界增长要用平台期区分。

十、修复、验证与回滚

修复可能包括限制缓存、关闭资源、降低并发、修正对象生命周期、调整堆比例、增加副本或容量。灰度发布后,至少跨越一个历史故障周期验证 RSS/PSS、GC、延迟、吞吐、重启次数和 memory.events。若错误率、GC 暂停或节点压力恶化,立即回滚版本或恢复原资源配置。

十一、容量治理

  • 以峰值工作集而不是平均值规划,并为单节点故障保留冗余。

  • 监控 MemAvailable、PSI、swap in/out、容器 working set、OOM 次数和应用堆。

  • 告警同时包含趋势、增长速度和持续时间,避免只按百分比报警。

  • 限制批任务并发,设置缓存上限和连接池上限,定期做故障演练。

十二、现场检查清单

  • □ 已确认主机、cgroup、容器还是应用 OOM

  • □ 已保存内核日志、进程排名和资源快照

  • □ 已将内存趋势与流量、发布和批任务对齐

  • □ 止血动作有回滚条件,没有盲目清缓存或重启

  • □ 修复经过灰度并跨越历史故障周期

  • □ 容量、限额、告警和防复发行动项已有负责人

总结

OOM 排查的核心是先找准限制层级,再用日志和时间序列证明内存去向。保存现场、最小化止血、趋势分析、灰度验证和容量治理缺一不可。

原创

Linux 服务器频繁 OOM:从现场证据、内存泄漏到容量治理

本文链接: Linux 服务器频繁 OOM:从现场证据、内存泄漏到容量治理

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

评论交流

文章目录