🐧 第五章 内存管理
📄 一句话概括

内存不够时内核按固定顺序做三件事:回收缓存、换到磁盘、杀进程。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 秒实验

  1. dmesg -T | grep -i 'out of memory',看这台机器有没有被 OOM 杀过进程。
  2. cat /proc/sys/vm/overcommit_memory,看这台机器的超卖策略(0 默认估算、1 永远答应、2 严格)。
  3. cat /proc/sys/vm/swappiness,看内核对 Swap 有多积极(0~100)。

常见误区

  1. 【内存不足就是物理内存用完了】。常常是地址空间承诺到顶,或者凑不出连续的页。
  2. 【配了 Swap 就安全了】。Swap 只是把杀进程往后推;推得太多会整机抖动,比被杀还难受。
  3. 【OOM Killer 是随机杀进程】。它按占用与紧急程度打分,分数最高的先死。
自测三问(点开对答案) 1. 内核处理内存不足的三步顺序是什么?为什么要这个顺序? —— **回收干净页 → 换出到磁盘 → 杀进程**。前两步只让你变慢,**第三步会让你丢工作**,所以放在最后。 2. OOM Killer 按什么挑人? —— 按**占用内存大小与紧急程度打分**,分数最高的先死。**内核线程和关键服务会手下留情。** 3. 为什么服务器上 Swap 通常配得不大? —— Swap 太大会让系统在内存严重不足时**反复换入换出(抖动)**,**整机假死**。宁可让 OOM 快速解决。

相关笔记