跳转至

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

标记可牺牲进程

# 缓存类进程可以优先牺牲
echo 500 > /proc/<pid>/oom_score_adj

程序中设置

#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 的风险

如果所有进程都设了 -1000:
  → OOM Killer 找不到可杀进程
  → 内核可能 panic 或系统卡死

只对真正关键的进程设 -1000(如 sshd、init)

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) 文档