内存不够时内核按固定顺序做三件事:回收缓存、换到磁盘、杀进程。OOM Killer 不是随机杀人,是按占用与紧急程度打分。
5.1 学会了看账,这一节看账不够时内核干什么——顺序不能换,因为三件事的代价差了几个数量级。
一、三步,顺序不能换
第一步:回收干净页。缓存和已经写回的文件页,丢掉就行,最便宜。 第二步:换出到磁盘(Swap)。把最久没用的页挪到盘上,要 IO,慢了但不用杀人。 第三步:杀掉一个进程(OOM Killer)。最后手段,杀掉就再也回不来。 为什么不能先杀进程:前两步代价是【变慢】,第三步代价是【丢工作】。只要能变慢解决,就不该丢工作。
二、OOM Killer 怎么挑人
它给每个进程打一个分,主要看两件事:占了多少内存、是不是不紧急。 分数最高的先死。内核线程、root 的关键服务会被手下留情(有调整余量,不是绝对豁免)。 结果:最常见的是某个把内存涨上去的用户进程被杀,而不是系统服务。 这也解释了为什么【容器里跑着跑着进程没了】——容器有内存上限,超了就是这一套流程。
三、明明还有内存,为什么报不足
三种完全不同的情况,现象像但原因不同。 一、MemFree 接近 0:其实还有大量可回收缓存,这不是不足。 二、地址空间被提交光了:物理内存还有,但【承诺出去的量】到顶了——Linux 的 overcommit 策略会拦住新的申请。 三、碎片化:空闲页总量够,但凑不出连续的若干页(大页申请最容易撞这个)。 所以查内存不足,第一件事是分清属于哪一种,否则越优化越偏。
四、Swap 在这里的位置
Swap 是第二步的工具:它把【杀进程】往后推。 但推得太狠有反效果:内存严重不够时,系统会反复换入换出,磁盘一直转,什么活都干不完——这叫抖动,用户感觉是【假死】。 所以服务器上 Swap 一般配得不大,宁可让 OOM Killer 快速解决,也不要让整机慢死。(5.15 详述)
五、动手 Demo:内存不足——不够了先回收,回收不动就杀人
这一节用一个 2048 MB 的模拟机演一遍:连着来五个请求,看内核每一步选了哪条路。
total, cache = 2048, 512
procs = {"A": 400, "B": 350, "C": 250, "D": 200}
for r in (400, 900, 300, 700, 1200):
avail = total - sum(procs.values()) - cache
if avail >= r:
act = "直接给"
elif avail + cache >= r:
cache -= r - avail
act = "回收缓存 %d MB" % (r - avail)
else:
cache = 0
while total - sum(procs.values()) < r:
v = max(procs, key=lambda k: procs[k])
act = "杀掉 " + v
del procs[v]
procs["新%d" % r] = r
print(r, avail, act)
实跑输出:
--------------------------------------------------------------------
内存不足——不够了先回收,回收不动就杀人
--------------------------------------------------------------------
① 演一遍(单位 MB,机器 2048,其中 512 是随时可回收的缓存)
请求 当时可用 内核做了什么
----------------------------------------------------------------------------
400 MB 336 MB 回收缓存 64 MB 后给
900 MB 0 MB 回收缓存 448 MB,杀掉 A(400 MB)、新400(400 MB)
300 MB 348 MB 直接给
700 MB 48 MB 缓存已空,只能杀掉 新900(900 MB)
1200 MB 248 MB 缓存已空,只能杀掉 新700(700 MB)、B(350 MB)
② 内核的三步顺序(顺序不能换)
顺序 动作 为什么在这个位置
-----------------------------------------------------------------------------
第一步 回收干净页 缓存和文件页写回后就能丢,最便宜
第二步 换出到磁盘(Swap) 要 IO,慢了但不用杀人
第三步 挑一个进程杀掉(OOM Killer) 最后手段,杀掉就再也回不来
**OOM Killer 不是随机杀人**:它按【谁占得多、谁不紧急】打分,
**分数最高的先死。**内核进程和 root 的关键服务会被手下留情。
③ 为什么会【明明还有内存却报不足】
现象 真实原因 该看什么
---------------------------------------------------------------------
MemFree 接近 0 内存都拿去做缓存了 MemAvailable
单个进程被 OOM 杀 它一次性要得太多 dmesg 里的 oom 记录
系统整体卡死 换出太多,全在等磁盘 vmstat 的 si/so 两列
**最容易搞反的一句**:**内存不足不等于物理内存用完**。
**它常常是【地址空间被提交光了】或者【内核没能及时回收】。**
看第一行:要 400 MB、当时可用 336 MB——回收 64 MB 缓存就够了,不用换出、不用杀人。 看第二行:要 900 MB,缓存全吐出来也不够,于是开始杀进程——而且杀的是占得最多的那个。 看第五行:要 1200 MB,缓存早就空了,只能连着杀两个。 整串演完,内核一共只走了三步里的前两步半——这就是真实系统里绝大多数情况的样子:先想办法,最后才杀人。
📋 Linux 上确认 OOM 有没有发生过:dmesg -T | grep -i oom。被杀掉的进程名、当时的可用内存、打分都会打出来。
30 秒实验
dmesg -T | grep -i 'out of memory',看这台机器有没有被 OOM 杀过进程。cat /proc/sys/vm/overcommit_memory,看这台机器的超卖策略(0 默认估算、1 永远答应、2 严格)。cat /proc/sys/vm/swappiness,看内核对 Swap 有多积极(0~100)。
常见误区
- 【内存不足就是物理内存用完了】。常常是地址空间承诺到顶,或者凑不出连续的页。
- 【配了 Swap 就安全了】。Swap 只是把杀进程往后推;推得太多会整机抖动,比被杀还难受。
- 【OOM Killer 是随机杀进程】。它按占用与紧急程度打分,分数最高的先死。
自测三问(点开对答案)
1. 内核处理内存不足的三步顺序是什么?为什么要这个顺序? —— **回收干净页 → 换出到磁盘 → 杀进程**。前两步只让你变慢,**第三步会让你丢工作**,所以放在最后。 2. OOM Killer 按什么挑人? —— 按**占用内存大小与紧急程度打分**,分数最高的先死。**内核线程和关键服务会手下留情。** 3. 为什么服务器上 Swap 通常配得不大? —— Swap 太大会让系统在内存严重不足时**反复换入换出(抖动)**,**整机假死**。宁可让 OOM 快速解决。- ☐ 5.3 简单的内存分配
相关笔记
- 内存相关的统计信息 — 先学会看账
- Swap — 第二步的工具细讲
- 存在多个逻辑 CPU 时的调度 — 另一个【资源到顶】的场景
- 🐧 Linux是怎样工作的 · 索引