第7章 内存:内存满,不等于内存已经成了瓶颈

内存判断的关键在于分清正常利用、回收压力和真正进入关键路径的瓶颈。

本页目录

内存看起来很满,先别急着说内存有问题

第 7 章先拦住另一个高频误判:内存占用高,就默认已经有内存压力。

Linux 这里还有一个很容易把人带偏的前提:应用先申请到的往往只是虚拟内存,不是已经兑现的物理内存。很多压力要等到真正写入、真正触发缺页以后,才会从“看起来还能撑”突然变成“怎么一下就 OOM 了”。

内存高占用,常见有几种完全不同的来源:

  • 页缓存把空闲内存吃满
  • 应用主动缓存和预热变多
  • 分配器保留了很多内存
  • 工作集真的变大了
  • 回收、缺页、swap、OOM 已经开始伤人

前四种和最后一种,工程含义完全不同。

先把内存问题拆成占用、回收、缺页、交换

以后看内存,先按这个顺序判断:

1. 先看这只是占用高,还是已经开始抖

占用高本身不是结论。关键是系统有没有因为内存不够开始明显付出代价。

2. 再看回收和扫描是不是已经很重

回收变重,才说明系统开始为了维持内存平衡付出成本。

后台的 kswapd 扫描还不一定会直接把应用拖慢,但如果已经走到同步的 direct reclaim,应用线程就会被迫停下来帮系统找可回收页,这时延迟才会真的开始难看。

3. 再看缺页、swap、OOM 有没有出现

这些信号更硬。它们一出现,内存压力的可信度就高很多。

但这里也要再拆一层:minor fault 很常见,很多只是按需分配或映射建立;更值得警惕的是 major fault,它意味着这次缺页已经走到更重的路径,代价明显高得多。

4. 最后再判断是应用问题、系统缓存问题,还是配额问题

容器限额、分配器保留、页缓存增长,都会把表象变复杂。

别把缓存吃满、缺页和交换混成同一种内存问题

最常见的错法有四种:

  • 看到 free 很低,就直接说内存不够
  • 看到 RSS 很大,就直接说内存泄漏
  • 看到 swap 用过,就直接说内存根因成立
  • 在容器里只看占用,不看限额和回收行为

这些错法都把“占用”直接翻译成“压力”。

尤其是 swap,最容易把“历史上换出去过”看成“现在还在因为 swap 受罚”。真正更该盯的是当前是不是还在持续 swap in / swap out,而不是只看已经占了多少 swap。

回收代价、缺页和交换一起起来时,才更像内存压力

下面这些证据一起出现时,才更像内存已经进入关键路径:

  • 回收和扫描明显变重,甚至已经走到 direct reclaim
  • major fault 开始抬头,而不是只有正常的 minor fault
  • 当前 swap in / swap out 持续出现,或 OOM 已经开始发生
  • 延迟和这些信号同步恶化
  • 降低工作集或放宽配额以后,症状明显缓解

只看到“占用高”,通常还不够。

一个最典型的错法:把缓存吃满当成内存故障

现场里经常会看到“内存几乎满了”的截图,然后所有人开始沿着泄漏、扩容、重启去想。

但很多时候,真正变大的只是页缓存或应用缓存。它说明系统在利用内存,不一定说明系统已经开始痛。

真正要追的是:

  • 这些缓存到底是不是可回收的
  • 回收是不是已经变得昂贵,甚至让应用线程自己下场 reclaim
  • 缺页里升高的是不是 major fault
  • 当前 swap 和 OOM 是不是已经开始出现

这几步不补,内存判断很容易误伤。

先把占用、回收和交换分开记

先把“占用高”和“压力大”拆开

以后看到内存数字难看,先问自己:

  1. 这是缓存变多了。
  2. 这是工作集真变大了。
  3. 这是回收已经开始抖了。
  4. 这是 major fault、当前 swap、OOM 已经开始伤人了。

占用、回收、缺页、交换要一起看

至少同时保留这四类证据:

  • 占用结构
  • 回收和扫描
  • minor fault 和 major fault
  • 当前 swap 行为或 OOM

只看单一占用数字,很难判断对不对。

别把 fault 和 swap 写成一团

缺页不是天然坏消息。很多 minor fault 只是正常分配和映射建立,真正更像性能问题的是 major fault 抬头,因为它常常意味着更重的缺页代价。

swap 也是一样。swpd 这类“已经用了多少 swap”更像历史结果;更该警惕的是系统现在是不是还在持续换入换出。前者说明系统曾经紧过,后者才更像当前性能正在被拖慢。

还有一个常被全局平均掩盖的坑: NUMA 局部干旱。整机看起来内存还很多,不代表某个节点的本地内存没被打空。多路机器上,只要单个节点先干了,应用就可能开始承受远端访问、局部回收,甚至直接在局部压力下触发 OOM。看整机大盘不够,必要时要分节点看。

把证据落到工具上

如果你已经怀疑内存开始伤业务,就别停留在概念上,直接去找对应证据:

  • 怀疑只是 free 低但其实是缓存吃满:先看占用结构,不要只盯总量
  • 怀疑扫描和回收已经很重:看 sar -B 这类能反映分页活动的指标
  • 怀疑已经走到 direct reclaim:用 drsnoop 这类工具抓应用线程被同步回收卡住的毛刺
  • 怀疑 swap 正在伤性能:看 vmstat 1 里的 siso,不要只看 swpd
  • 怀疑内存压力已经真实拖慢任务:看 /proc/pressure/memory,比只看占用更接近“业务到底被卡了多久”

容器环境先补限额和 reclaim 证据

在容器里,先补:

  • 内存限额
  • reclaim 行为
  • cgroup 级别的压力信号
  • 页缓存是否也被算进这层限制
  • OOM 触发边界

这一层不补,很多裸机直觉都会失真。尤其在容器里,页缓存也可能直接占掉 cgroup 可用内存,所以“只是缓存吃满”在裸机上也许还只是利用率高,在容器里却可能已经逼近回收、swap 甚至 OOM。

云和容器环境还有一条判断边界要提前立住: 很多环境根本没有 swap 这层缓冲带。没有 swap 时,内存耗尽不一定先经历一段明显的“系统越来越抖”,而可能是突发分配一上来就直接 OOM。别等“先看到 swap 抖动再预警”,那时可能已经晚了。

这一章最该记住的 8 个判断

  1. free 很低通常只是 Linux 在积极利用内存,不是已经证明内存不够。
  2. malloc 成功不代表物理内存真的够,很多风险会拖到真正写入时才爆出来。
  3. minor fault 常常是正常现象,major fault 更像值得追的性能证据。
  4. swpd 大说明历史上紧过,不等于现在还在被 swap 拖慢。
  5. 真正危险的是 direct reclaim,因为它会把应用线程直接拉进回收路径。
  6. 内存压力是否真的伤业务,最好看 stall,而不只是看占用。
  7. 容器里的页缓存也会占 cgroup 配额,所以“只是缓存”未必安全。
  8. OOM 更像故障结果,不只是“压力有点大”的一种普通信号。
  9. RSS 持续变大,也不自动等于代码泄漏;共享库重复计入和分配器保留内存都会把表象抬高,必要时要看 PSS,而不是只看 RSS。

内存分清以后,接着追这几个问题

  • 现在看到的难看内存数字,究竟是缓存、工作集、回收,还是配额边界?
  • 当前升高的是 minor fault 还是 major fault?
  • 系统只是历史上用过 swap,还是此刻还在持续 swap in / swap out?
  • 应用延迟抖动时,是后台回收在忙,还是线程已经被 direct reclaim 卡住?
  • 容器被打爆时,真正吃掉边界的是匿名内存,还是页缓存?
  • 如果把工作集缩小一点、把限额放宽一点,症状会不会明显缓解?

内存分清以后,再看 I/O 路径哪一层先慢

第 7 章解决的是“内存高到底算不算压力”。第 8 章接着进入 I/O 软件路径,先从文件系统这一层拆开。