概述

Linux 系统中,当物理内存和交换空间不足时,内核会触发 Out of Memory Killer(OOM Killer)机制,主动终止占用内存最多的进程以释放资源。这是一把”双刃剑”——它能防止系统完全卡死,但也可能导致关键业务进程被意外杀死,造成服务中断。本文从排查思路、应急处理、根因分析四个维度,介绍如何系统地应对 OOM 问题。

一、快速识别 OOM 发生

1.1 dmesg 查看内核日志

OOM 事件会记录在内核环形缓冲区,通过 dmesg 即可快速确认:

1
2
3
4
5
6
# 查看 OOM 相关内核日志
dmesg | grep -i "out of memory"
dmesg | grep -i "oom"

# 典型输出示例
[123456.789012] Out of memory: Killed process 12345 (myapp) total-vm:2048000kB, anon-rss:1024000kB, file-rss:0kB

日志中会显示被杀死进程的 PID、进程名、虚拟内存总量(total-vm)和实际物理内存占用(anon-rss)。

1.2 查看 OOM Score

每个进程的 /proc/<PID>/oom_score 文件记录了该进程被 OOM Killer 选中的优先级分数(0~1000),分数越高越容易被杀:

1
2
3
4
5
6
# 查看所有进程的 OOM 分数
awk '{print $1,$2,$NF}' /proc/*/oom_score 2>/dev/null | sort -rn | head -20

# 查看指定进程的 OOM 分数和配置
cat /proc/$(pidof myapp)/oom_score
cat /proc/$(pidof myapp)/oom_score_adj # 手动调整分数(范围 -1000 ~ 1000)

1.3 查看系统内存状态

1
2
3
4
5
6
7
8
# 内存使用概览
free -h

# 详细内存和交换空间信息
cat /proc/meminfo

# 按内存占用排序查看进程
ps aux --sort=-%mem | head -20

二、应急处理流程

2.1 立即止血——恢复被杀死的重要进程

如果业务进程已被 OOM Kill,首先重启服务:

1
2
3
4
5
6
7
8
# 查看系统日志确定被杀的进程和时间
journalctl -xe --since "1 hour ago" | grep -i oom

# 重启对应服务
systemctl restart <service_name>

# 检查服务状态
systemctl status <service_name>

2.2 临时释放内存

在定位根因期间,可通过以下方式临时缓解内存压力:

1
2
3
4
5
6
7
8
9
10
11
# 清除页面缓存(需 root)
echo 3 > /proc/sys/vm/drop_caches

# 强制使用交换空间(让内存得到喘息)
swapoff -a && swapon -a

# 临时增加交换空间(应急)
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

2.3 防止关键进程被杀死

通过 oom_score_adj 可以保护重要进程,降低其被 OOM Killer 选中的概率:

1
2
3
4
5
6
7
8
# 禁止 OOM Killer 杀死指定进程(设为 -1000)
echo -1000 > /proc/$(pidof myapp)/oom_score_adj

# 永久生效(写入 sysctl.conf)
echo "vm.mysql_score_adj = -1000" >> /etc/sysctl.conf

# 永久禁止对某个进程组杀灭(使用 cgroup)
echo -1000 > /sys/fs/cgroup/memory/myapp_group/memory.oom_control

三、根因分析

3.1 分析内存消耗来源

1
2
3
4
5
6
7
8
# 按实际物理内存排序查看进程
ps aux --sort=-%mem | awk '{print $2,$3,$4,$11}' | head -30

# 查看进程的内存映射详情
pmap -x $(pidof myapp) | sort -k3 -n -r | tail -20

# 查看共享内存使用
ipcs -m

3.2 判断是内存泄漏还是正常高负载

内存泄漏特征: 进程内存只增不减,即使服务空闲也不释放。

正常高负载特征: 进程内存在某水位稳定,缓存随负载升降。

1
2
3
4
5
6
# 多次采样观察内存趋势(每 30 秒一次)
for i in {1..10}; do
date
ps aux --sort=-%mem | head -10
sleep 30
done

3.3 常见内存耗尽场景

场景 典型表现 排查命令
Java/Node.js 堆内存未限制 进程虚拟内存持续增长 jstat -gc <pid> / node --max-old-space-size
临时文件未清理 /tmp 或 /var/tmp 文件堆积 du -sh /tmp/*
共享内存未释放 IPC 对象残留 ipcs -m
进程 fork 炸弹 大量子进程创建 `ps aux
缓存未及时清理 Buff/Cache 占用过高 free -h 中 available 列

四、预防措施

4.1 配置 swap 空间

1
2
3
4
5
6
7
8
# 检查当前 swap
swapon -s

# 生产环境建议 swap 大小(物理内存 < 64GB 时配置物理内存的 50%~100%)
fallocate -l 8G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

4.2 设置 vm.overcommit_memory

1
2
3
4
5
6
7
8
# vm.overcommit_memory = 0(默认,允许适度过量分配)
# vm.overcommit_memory = 1(始终允许,不检查)
# vm.overcommit_memory = 2(精确检查,不允许超过可用内存 + 50%)

# 推荐值:设为 2 并设置合理的 overcommit_ratio
echo "vm.overcommit_memory=2" >> /etc/sysctl.conf
echo "vm.overcommit_ratio=80" >> /etc/sysctl.conf
sysctl -p

4.3 限制容器/进程的内存

Docker 场景:

1
2
3
4
5
6
# docker-compose.yml
services:
myapp:
mem_limit: 2g
mem_reservation: 1g
oom_kill_disable: false # 建议 false,让 OOM Killer 正常运作

cgroup 限制:

1
2
3
# 为进程组设置内存上限
echo "4G" > /sys/fs/cgroup/memory/myapp_group/memory.limit_in_bytes
echo "3G" > /sys/fs/cgroup/memory/myapp_group/memory.soft_limit_in_bytes

4.4 部署 OOM 告警

提前感知内存压力,避免等 OOM 发生后才处理:

1
2
3
4
5
6
7
# 监控脚本(内存使用 > 90% 时告警)
#!/bin/bash
USED=$(free | grep Mem | awk '{print int($3/$2 * 100)}')
if [ $USED -gt 90 ]; then
echo "内存使用率: ${USED}% - OOM 风险高!"
# 这里可以接入钉钉/飞书/邮件告警
fi

总结

OOM 问题处理的关键在于”快识别、止血快、根因清、防复发”。大多数 OOM 悲剧并非系统资源真的不够,而是缺乏监控预警和合理的内存限制配置。建议在日常巡检中将内存使用率告警纳入必备项,并为关键服务配置 oom_score_adj 保护,同时通过 cgroup 或容器资源限制防止单一服务耗尽整机内存。