OOM (Out of Memory)¶
是什么 / 解决什么问题¶
当系统物理内存和 swap 空间都耗尽时,Linux OOM Killer 会选择并终止进程来释放内存。理解 OOM 的触发条件和选择逻辑,有助于预防和诊断生产环境中的进程被杀问题。
行为与语义¶
OOM 触发条件¶
可用内存 ≈ 0 + swap 空间不足
→ 内核尝试回收页面(page cache、inactive pages)
→ 回收失败
→ 触发 OOM Killer
→ 选择一个进程杀掉
→ 释放其内存
OOM 评分机制¶
每个进程有 oom_score (0-1000):
基础分 = (进程 RSS + swap使用) / 系统总内存 * 1000
+ oom_score_adj 调整 (-1000 到 1000)
oom_score 最高的进程被杀
# 查看进程的 OOM 分数
cat /proc/<pid>/oom_score # 当前计算出的分数
cat /proc/<pid>/oom_score_adj # 手动调整值 (-1000~1000)
oom_score_adj 特殊值¶
| 值 | 效果 |
|---|---|
| -1000 | 完全禁止被 OOM Kill |
| -500 | 显著降低被杀概率 |
| 0 | 默认 |
| +500 | 显著提高被杀概率 |
| +1000 | 总是最先被杀 |
使用模式¶
保护关键进程¶
# 保护数据库进程不被 OOM Kill
echo -1000 > /proc/<pid>/oom_score_adj
# systemd 服务配置
# /etc/systemd/system/mydb.service
[Service]
OOMScoreAdjust=-1000
标记可牺牲进程¶
程序中设置¶
#include <stdio.h>
#include <fcntl.h>
void set_oom_adj(int score) {
char path[64];
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", getpid());
int fd = open(path, O_WRONLY);
dprintf(fd, "%d", score);
close(fd);
}
// 保护自己
set_oom_adj(-1000);
禁用 OOM Killer(不推荐)¶
# per-cgroup 禁用
echo 1 > /sys/fs/cgroup/mygroup/memory.oom.group
# 系统级(panic 而非 kill)
sysctl vm.panic_on_oom=1
# 内存耗尽时内核 panic(适合宁死不杀的场景)
cgroup 内存限制(推荐方式)¶
# cgroup v2: 限制进程组内存使用
echo 2G > /sys/fs/cgroup/myapp/memory.max # 硬限制
echo 1G > /sys/fs/cgroup/myapp/memory.high # 软限制(超过后回收压力增大)
# 超过 memory.max → cgroup 内的进程被 OOM Kill
# 不影响系统其他进程
性能考量¶
预防 OOM 比处理 OOM 重要¶
策略优先级:
1. 容量规划 — 确保有足够内存
2. 内存限制 — cgroup memory.max 限制单应用
3. 监控告警 — 内存使用超阈值时告警
4. OOM adj — 保护关键进程
5. 优雅降级 — 应用层内存压力感知
内存压力感知(PSI)¶
# Pressure Stall Information (4.20+)
cat /proc/pressure/memory
# some avg10=0.00 avg60=0.00 avg300=0.00 total=0
# full avg10=0.00 avg60=0.00 avg300=0.00 total=0
# some: 至少一个任务因内存等待
# full: 所有任务都因内存等待
// 程序化监听内存压力
#include <linux/psi.h>
int fd = open("/proc/pressure/memory", O_RDWR);
// 注册阈值触发通知(10μs 窗口内 full 超过 100ms)
write(fd, "full 100000 1000000", 20);
// 用 poll/epoll 监听 fd,触发时做内存释放
swap 的影响¶
# 查看 swap 使用
swapon --show
free -h
# swappiness 控制(0-100)
cat /proc/sys/vm/swappiness
# 默认 60
# 0: 尽量不用 swap(只在 OOM 前才用)
# 100: 积极使用 swap
# 数据库/低延迟服务通常设为 0-10
sysctl vm.swappiness=10
常见陷阱与 FAQ¶
1. OOM Kill 日志¶
# 内核日志中查看 OOM 事件
dmesg | grep -i "oom\|killed process"
# 典型输出:
# Out of memory: Killed process 1234 (myapp) total-vm:5000000kB,
# anon-rss:4000000kB, file-rss:50000kB, shmem-rss:0kB,
# oom_score_adj:0
2. 被 cgroup OOM 杀 vs 系统 OOM 杀¶
系统 OOM: /var/log/kern.log 中 "Out of memory"
cgroup OOM: dmesg 中 "Memory cgroup out of memory"
cgroup OOM 不一定意味着系统内存不足
只是该 cgroup 超过了其 memory.max 限制
3. oom_score_adj=-1000 的风险¶
4. 内存泄漏导致 OOM¶
常见模式:
- 应用缓慢泄漏内存
- 几天/几周后触发 OOM
- 进程被杀,重启后又缓慢泄漏
排查:
- 监控 RSS 趋势
- valgrind / ASan 检测
- jemalloc profiling
5. fork 导致的假 OOM¶
overcommit_memory=2(严格模式)下:
大内存进程 fork 时需要预留双倍 commit
可能触发 ENOMEM(fork 失败)
即使子进程立即 exec(实际不需要那么多内存)
解决:overcommit_memory=0 或使用 posix_spawn
观测与调试¶
# 实时查看内存状态
watch -n1 'free -h; echo "---"; cat /proc/meminfo | grep -E "MemAvail|SwapFree|Committed"'
# 查看哪些进程消耗最多内存
ps aux --sort=-rss | head -20
# 查看 OOM 历史
journalctl -k | grep -i oom
# 查看 cgroup 内存事件
cat /sys/fs/cgroup/myapp/memory.events
# low: 触发 memory.low 的次数
# high: 触发 memory.high 的次数
# max: 触发 memory.max 的次数
# oom: OOM 次数
# oom_kill: 被杀进程数
# 模拟内存压力(测试 OOM 行为)
stress-ng --vm 1 --vm-bytes 90% -t 60s
延伸阅读¶
man 5 proc— /proc/[pid]/oom_score, oom_score_adj- Documentation/cgroup-v2.txt — cgroup 内存控制
/proc/sys/vm/overcommit_*— overcommit 参数- PSI (Pressure Stall Information) 文档