Linux 内核设计了一套内存保护机制,当系统内存资源几乎耗尽、无法通过回收或换出满足新的内存请求时,会强制终止一个或多个用户进程,这个机制被称为 OOM Killer。很多情况下,服务进程突然消失、内核日志出现 Out of memory 字样,就是它触发的表现。理解其触发原因,需要从内存分配路径、内存回收和内核评分逻辑三个角度入手。

一、OOM Killer 的触发条件与进程选择逻辑
OOM Killer 并不会在 free 命令显示内存为 0 时才触发。内核在分配内存时会经过一条比较复杂的路径:先尝试从空闲列表分配,如果失败则进入慢速路径,也就是内存回收。回收动作包括清理页缓存、回收 slab、把匿名页写入 swap 等。只有当这些回收手段仍然无法释放出足够的物理内存,并且系统没有配置 cgroup 级硬限制时,全局 OOM Killer 才会启动。因此,触发 OOM 的本质不是物理内存完全用光,而是内存压力已经超过内核可承受的阈值。
另一个容易忽略的触发来源是内存碎片。当内核需要分配高阶连续页面,比如创建大页、某些驱动需要连续 DMA 缓冲区时,即使系统有较多零散空闲内存,只要无法满足连续的物理页需求,也可能出现分配失败。在默认的 vm.overcommit_memory 策略下,malloc 经常返回成功,但真正写入内存时才会触发 OOM,这也是为什么一些应用在启动阶段一切正常,运行一段时间后才被杀。
内核挑选被终止进程的依据是 oom_score。这个分数由进程当前占用的内存量、是否消耗了大量页表、运行时长、进程优先级等因素综合计算。分数越高,越容易被选中。用户态可以通过 /proc/PID/oom_score_adj 手动调整这个分数,范围从 -1000 到 1000,-1000 表示完全不会被 OOM Killer 选中。如果进程运行在某个 cgroup 内,并且该 cgroup 设置了内存上限,那么触发的是局部 OOM,只会从该 cgroup 的进程中选择,不会影响宿主机上的其他进程。
下面命令可以查看内核全局 OOM 相关参数,帮助确认触发原因:
cat /proc/sys/vm/overcommit_memory cat /proc/sys/vm/panic_on_oom cat /proc/sys/vm/swappiness cat /proc/buddyinfo
其中 overcommit_memory 的值 0 表示启发式判断,1 表示总是允许超额分配,2 表示严格限制,超过可用内存和 swap 总量时直接返回失败。如果系统频繁 OOM,检查这个值为 0 或 1 可能会让问题更隐蔽。
二、通过内核日志和运行时文件定位被杀进程
OOM Killer 触发后,内核会打印详细日志。在较新的内核中,通常可以从 dmesg 或 journalctl 中搜索 killed process 关键字。日志里会包括被杀进程的名字、PID、当时的 oom_score_adj,以及内存使用统计。例如 dmesg -T 输出中会出现类似 Out of memory: Killed process 3301 (java) total-vm:1234567kB, anon-rss:890123kB 这样的信息。看到进程名后,可以结合业务日志判断被杀时该进程在执行什么任务。
除了内核日志,运行时文件也能提供很多线索。/proc/meminfo 中的 CommitLimit 和 Committed_AS 可以判断当前内存承诺是否已经接近上限;/proc/vmstat 中的 oom_kill 字段记录了全局 OOM 发生次数;/proc/pressure/memory 给出内存压力指标,适合持续监控。对于怀疑经常触发 OOM 的系统,可以执行下面的命令快速查看历史事件:
dmesg -T | grep -i -E 'killed process|out of memory' journalctl -k --since "1 hour ago" | grep -i oom grep -i 'oom\|killed' /var/log/messages
定位到具体进程后,可以通过 /proc/PID/oom_score 查看当前危险程度。数值越高的进程越容易被杀。oom_score_adj 则是用户可调的偏移量,默认值通常是 0。以下命令可以找出当前系统中 oom_score 最高的前几个进程:
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
if [ -r /proc/$pid/oom_score ]; then
score=$(cat /proc/$pid/oom_score 2>/dev/null)
comm=$(cat /proc/$pid/comm 2>/dev/null)
echo "$score $pid $comm"
fi
done | sort -nr | head -10这个脚本会遍历所有 PID,读取 oom_score 并按从大到小排序。它可以帮助管理员在 OOM 真正发生前识别高风险进程,而不是等到服务中断后再从日志里找原因。
三、处理思路与预防 OOM 的配置实践
处理 OOM 问题通常分两步:先止损,再预防。止损阶段,如果确认某个关键业务进程不能被误杀,可以临时调低它的 oom_score_adj。例如对数据库进程执行 echo -1000 > /proc/$(pidof mysqld)/oom_score_adj,可以让内核在全局 OOM 时优先选择其他进程。但要注意,这样做只是保护单个进程,如果系统内存压力持续存在,内核仍然会杀其他进程,甚至可能导致系统无响应。更稳妥的做法是同时重启占用内存异常的应用,并尽快增加物理内存或启用 swap。
预防阶段可以从内核参数和进程级限制两方面入手。对于容易突然申请大量内存的程序,建议使用 cgroup v2 或 systemd 的内存限制,避免单个进程拖垮整个系统。以 systemd 服务为例,可以在服务单元文件中加入 MemoryMax 和 MemoryHigh,限制内存峰值。例如:
[Service] ExecStart=/usr/bin/java -jar app.jar MemoryMax=2G MemoryHigh=1.6G
MemoryHigh 是软限制,当内存超过该值时会触发更积极的回收;MemoryMax 是硬限制,超过后 cgroup 内会触发局部 OOM。这样即使某个应用内存泄漏,最坏情况也只是这个服务被杀,不会影响同一台服务器上的其他服务。
内核参数方面,如果业务程序会主动检查内存分配结果,可以将 vm.overcommit_memory 设置为 2,并调整 vm.overcommit_ratio。这样内核会在内存承诺超限时直接让 malloc 返回失败,而不是等真正使用内存时触发 OOM。如果系统容易因为内存压力导致死锁,可以将 vm.panic_on_oom 临时设置为 1,让系统在 OOM 时直接重启,这对于无人值守的服务器有时比卡死更可控。长期来看,合理配置 swap 大小和 vm.swappiness 也很重要。对于大部分 Linux 服务器,将 swappiness 设为 10 到 30 可以在内存紧张时优先回收页缓存,减少不必要的 swap I/O。最终,如果业务负载确实需要更多内存,增加物理内存才是最根本的解决办法。
监控同样不可忽视。建议持续收集 /proc/meminfo 中的 MemAvailable、/proc/pressure/memory 以及 cgroup 的 memory.events,当内存压力在几分钟内持续升高时提前告警。结合日志中的 oom_kill 次数,可以判断当前配置下的内存容量是否满足业务需求,避免反复出现 OOM Killer 误杀关键进程的情况。
OOM Killer内存回收Linux内核修改时间:2026-08-27 00:15:39