想象你是厨房总管,灶台(物理内存)就是你的全部操作空间:案板越大,能同时处理的食材越多。可灶台永远不够大,当菜越来越多,台面堆满时,你该怎么办?聪明的厨师会有两招:把不常用的食材用真空压缩袋收进台面角落的收纳盒(这就像 zRAM 内存压缩);再把一年用不到几次的工具搬去地下室储物间(这就像 swap 交换空间)。可地下室取用一趟要跑楼梯,收纳盒虽然省地儿却也要占一点台面,到底怎么取舍?这就是内存管理调优的核心命题。本文将用厨房收纳的视角,带你从零看懂 zRAM 与 swap 的协作机制——从“什么该扔、什么该搬”的换页基础,到压缩算法的省地儿技巧,再到内核的回收偏好旋钮,最后落到真实场景的调优决策。

岗位关联

对 AI 从业者而言,理解 zRAM 与 swap 调优意味着:在 GPU 服务器、推理服务或数据管道面临 OOM 抖动时,你能精准定位是“台面不够”还是“收纳不当”,用最小成本把系统从崩溃边缘拉回来。

目录
  1. 可视化演示
  2. 交换基础:内存压力的源头
  3. zRAM原理:台面上的真空压缩柜
  4. Swap配置:地下室的搭建与类型
  5. 内核调优:管家手中的旋钮
  6. 监控分析:厨房的温度计与仪表
  7. 实战策略:不同厨房的装修方案
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

交换基础:内存压力的源头

厨房台面再大也有极限。当你同时煎牛排、煮汤、切配菜时,台面被占满,新的食材无处安放——这就是“内存不足”。操作系统把内存切成 4KB 大小的“页”,就像台面被划成一个格子,每个进程的代码、数据、栈都占若干格。当新进程需要内存而格子不够时,内核必须赶走一些旧格子的内容:这就是“回收”。

可“赶走”也分两类。一类是文件缓存页——它们是磁盘上文件的副本,比如刚读过的配置文件、图片数据,相当于干货罐头,扔了也不心疼,因为随时能从仓库(磁盘)重新拿。另一类是匿名页——进程的堆、栈、运行数据,相当于正在锅里翻炒的鲜虾,扔了就直接损失一锅菜。对于匿名页,内核不能简单丢弃,只能先“搬走”保存:搬到压缩柜(zRAM)或地下室(swap),这一过程叫“换出”(swap out),等进程再次需要时再“换入”(swap in)。

内核通过 LRU 链表记录每页的“冷热”——就像厨师在小本本上记着哪样食材最近一直没用。内存压力越大,内核越要频繁地翻本子、挑冷页、执行回收。

理解了“什么能扔、什么要搬”,再看 zRAM 就顺理成章了——它正是“能搬就搬、但搬在台面内”的聪明收纳。

内存压力=Uanon+Ufile_cache+UkernelMtotal\displaystyle \text{内存压力} = \frac{U_{\text{anon}} + U_{\text{file\_cache}} + U_{\text{kernel}}}{M_{\text{total}}}
这个公式告诉你:内存压力不止看“用了多少”,还要看构成——匿名页、文件缓存和内核开销。其中匿名页是必须搬走的硬骨头,文件缓存是可回撤的弹性空间。
  1. 匿名页占用 \(U_{\text{anon}}\)进程的私有数据,如堆、栈、匿名映射,无法从磁盘重建,是“正在锅中翻炒的菜”,必须换出才能腾出格子。
    Uanon=进程私有页总量U_{\text{anon}} = \text{进程私有页总量}
  2. 文件缓存 \(U_{\text{file\_cache}}\)磁盘文件的内存副本,如程序代码、日志缓存,可随时从磁盘重新读入,是“干货罐头”,可直接丢弃。
    Ufile_cache=文件页 + 页缓存 + 临时映射U_{\text{file\_cache}} = \text{文件页 + 页缓存 + 临时映射}
  3. 内核开销 \(U_{\text{kernel}}\)内核自身代码、描述符、页表等数据结构,基本不可回收。
    Ukernel=内核堆 + 栈 + 核心数据结构U_{\text{kernel}} = \text{内核堆 + 栈 + 核心数据结构}
  4. 物理内存总量 \(M_{\text{total}}\)整个配方分母,压力公式的基准。
    Mtotal=RAM 总容量M_{\text{total}} = \text{RAM 总容量}

zRAM原理:台面上的真空压缩柜

既然匿名页不能丢,那能不能让它少占地方?zRAM 的灵感正来自厨师的真空压缩袋:把不常用的青菜抽成扁片,塞进台面角落的收纳盒,虽然还占一点台面,但省出的空间远超成本。zRAM 是内核提供的一块内存压缩块设备——它本质是虚拟磁盘,但“磁盘”本身就在物理内存里,写入它的数据会被实时压缩。

工作流程像这样:系统冷启动后,你可以把 zRAM 设备格式化为 swap 设备并启用(mkswap /dev/zram0 + swapon)。内存压力升高时,内核把匿名页写进 zRAM,页面先被压缩再存储。当进程再次访问该页时,从 zRAM 读取、解压、重新放回普通内存——整个过程不碰硬盘,延迟远低于磁盘 swap。

压缩算法上,内核常用 LZ4 和 ZSTD:LZ4 压缩速度快、CPU 开销小,适合延迟敏感场景;ZSTD 压缩率更高但计算稍重。还有一项非常巧妙的“零页优化”:如果页面内容全是 0(比如刚分配的堆内存),内核不会真压缩,而是直接记录为一个零页标记,连压缩都省了。想象一箱空矿泉水瓶,折叠起来几乎不占地方。

zRAM 解决了“台面省地”的问题,但如果压缩柜也满了,或者需要腾出更大的空间,就得动真格地把东西搬去地下室——那就是 swap 交换空间的主场。

节省量=Sraw×(1−ScompSraw)=Sraw−Scomp\displaystyle \text{节省量} = S_{\text{raw}} \times \left(1 - \frac{S_{\text{comp}}}{S_{\text{raw}}}\right) = S_{\text{raw}} - S_{\text{comp}}
压缩收益取决于内容可压缩性。文本、稀疏数组压缩率极高;已经压缩过的视频、图片数据再压效果很差,这是 zRAM 调优要考虑的现实约束。
  1. 原始大小 \(S_{\text{raw}}\)页面未压缩前的物理尺寸(常为 4KB),相当于压缩前食材的体积。
    Sraw=原始页大小S_{\text{raw}} = \text{原始页大小}
  2. 压缩后大小 \(S_{\text{comp}}\)经过 LZ4/ZSTD 压缩后的实际占用,相当于抽真空后的体积。
    Scomp=压缩后字节数S_{\text{comp}} = \text{压缩后字节数}
  3. 压缩率 \(S_{\text{comp}}/S_{\text{raw}}\)越小说明压得越狠。典型文本页可压缩到 20%-50%,全零页直接趋近于零。
    ScompSraw≈0.2∼0.5\frac{S_{\text{comp}}}{S_{\text{raw}}} \approx 0.2 \sim 0.5
  4. 节省量 \(S_{\text{raw}} - S_{\text{comp}}\)每个页节省出的物理内存,所有页累加就是 zRAM 的整体收益。
    Sraw−Scomp=单页节省S_{\text{raw}} - S_{\text{comp}} = \text{单页节省}

Swap配置:地下室的搭建与类型

Swap 是操作系统在磁盘上预留的交换空间,相当于厨房的地下室储物间。当 zRAM 压缩柜和台面都不够用时,内核把最冷的匿名页搬进地下室,让台面腾出空间。swap 的搭建有两种方式:swap 分区是直接划一块裸磁盘区域,像装修时专门砌好的地下室,不经过文件系统,读写直来直去;swap 文件则是在现有文件系统里建一个大文件,像在地下室隔出的临时储物间,优点是无需重新分区、按需创建删除,但多一层文件系统开销。

现代系统中常常多个 swap 共存,这时“优先级”就是调度规则:每个 swap 设备可以通过 pri 参数设置优先级(数值越大越先使用),内核像管家一样,先让压缩柜(zRAM,通常设置高优先级)装满,再溢出到地下室(磁盘 swap)。这正是经典组合:zRAM 做第一层快交换,磁盘 swap 做第二层兜底。

那 swap 到底该配多大?实践中有一套常见估算规则,结合物理内存大小:

搭建完地下室,厨房还有个关键问题:管家在什么时候、以多大劲头把东西往地下室搬?这就要请出内核调优的核心参数了。

Sswap={2×MRAMMRAM≤2GBMRAM+2GB2GB<MRAM≤8GBmax⁡(8GB, MRAM2)MRAM>8GB\displaystyle S_{\text{swap}} =\begin{cases}2 \times M_{\text{RAM}} & M_{\text{RAM}} \leq 2\text{GB} \\M_{\text{RAM}} + 2\text{GB} & 2\text{GB} < M_{\text{RAM}} \leq 8\text{GB} \\\max\left(8\text{GB},\ \displaystyle\frac{M_{\text{RAM}}}{2}\right) & M_{\text{RAM}} > 8\text{GB}\end{cases}
这不是硬性公式,而是经验边界。小内存机器需要相对大的 swap 兜底;大内存机器本身余量大,swap 不必跟着膨胀。真正的调优还要看工作负载,比如数据库服务器可能完全不要 swap,而桌面系统则离不开它。
  1. 内存 ≤ 2GB 的规则小台面厨房必须有大地下室兜底,所以 swap 设为内存的两倍。
    Sswap=2×MRAMS_{\text{swap}} = 2 \times M_{\text{RAM}}
  2. 内存处于 2GB-8GB 的规则中等厨房台面有一定冗余,地下室只需“内存 + 2GB”。
    Sswap=MRAM+2GBS_{\text{swap}} = M_{\text{RAM}} + 2\text{GB}
  3. 内存 > 8GB 的规则大厨房台面本身就是优势,地下室容量取 8GB 与内存一半中的较大值,避免无意义的浪费。
    Sswap=max⁡(8GB, MRAM2)S_{\text{swap}} = \max\left(8\text{GB},\ \frac{M_{\text{RAM}}}{2}\right)

内核调优:管家手中的旋钮

真实厨房里,总有个精明的厨师长决定收纳策略:是优先把鲜肉打包(换出匿名页),还是优先把干货罐头扔掉(回收文件缓存)?Linux 内核把这种决策倾向浓缩为一个叫 swappiness 的旋钮。它的取值在较新内核中为 0-200(早期是 0-100),默认 60。值越大,内核越倾向把匿名页换出到 swap,而不是回收文件缓存;值越小,则尽量保住匿名页,先回收缓存页。

注意:swappiness 不是“什么时候开始用 swap”的开关,而是“匿名页相对文件页的回收权重”。把它理解成管家对两种收纳动作的偏好度,而不是一个二元的“启用/禁用”。另一个旋钮 vm.vfs_cache_pressure 控制目录项和 inode 缓存的回收力度,取值范围 0-200,默认 100。调大则内核更积极地回收文件系统元数据缓存,适合内存紧张但目录操作频繁的机器。

此外,内核维护着三条内存水位线:min(最低保证线)、low(预警线)、high(恢复线)。空闲内存降到 low 以下时,内核线程 kswapd 开始在后台异步回收,像帮工悄悄收拾台面;若进程分配内存时发现空闲内存已经跌破 min,就得亲自上场同步回收——这叫“直接回收”(direct reclaim),相当于满手面粉的厨师被迫停下炒菜先去清理台面,延迟飙升的元凶之一。

旋钮调好了,怎么知道厨房实际运转得如何?别靠猜,要用仪表盘——监控分析。

匿名页回收倾向=swappiness100∈[0, 2]\displaystyle \text{匿名页回收倾向} = \frac{\text{swappiness}}{100} \in [0,\ 2]
数值超过 1(即 swappiness > 100)表示即使在有可回收文件缓存的情况下,也愿意更激进地把匿名页赶去 swap。这种极端设置在普通服务器上很少用,但对某些内存压缩场景有特殊价值。
  1. 分子 swappiness你给出的偏好值,越大意味着越想把匿名页换出,为台面腾出位置。
    swappiness∈[0,200]\text{swappiness} \in [0, 200]
  2. 分母 100归一化因子,把偏好值映射到 0-2 的倍数。0.6 表示温和倾向(默认),1.5 表示激进倾向。
    swappiness100→回收权重\frac{\text{swappiness}}{100} \rightarrow \text{回收权重}

监控分析:厨房的温度计与仪表

调优的前提是看清现状。Linux 给予系统的“厨房仪表盘”主要有三块。

第一块是 free -h。它输出的 MemTotal、MemUsed、MemAvailable 一眼看全台面占用,其中 MemAvailable 是内核给你算好的“还能腾出多少空间”,比手动 free + buff/cache 更准确——因为它已经扣除了不可回收的部分。第二块是 vmstat。它实时滚动输出内存与 swap 的关键指标:si(swap in)和 so(swap out)表示每秒从地下室搬进来/搬出去多少 KB,这两个数字只要持续跳高,就说明系统正在频繁做“楼梯跑动”,IO 压力极大;pgpgin/pgpgout 则反映整体页面换入换出。

第三块是 zramctl,专看 zRAM 压缩柜状态:能看到原始数据量(orig_data_size)、压缩后数据量(compr_data_size)以及压缩率。如果压缩率只有 5%,说明存进 zRAM 的几乎都是已压缩数据(如视频流),这时的 zRAM 反而浪费 CPU;压缩率 40%-60% 则非常健康,相当于每页平均省下一半内存。

仪表盘清晰之后,最后一步是把方案落到不同厨房的规划中——实战策略。

Swap利用率=SusedStotal×100%\displaystyle \text{Swap利用率} = \frac{S_{\text{used}}}{S_{\text{total}}} \times 100\%
利用率是“地下室到底堆满多少”的直观指标。长期超过 60% 说明系统依赖交换来维持生存,此时单纯调参是治标,需要扩容或优化内存占用。利用率长期为 0 也表示 swap 白配了空间。
  1. 已用交换空间 \(S_{\text{used}}\)地下室已被占用的容量,来自 free 的 Swap used 列或 /proc/swaps 的 used 字段。
    Sused=Swap 已用字节S_{\text{used}} = \text{Swap 已用字节}
  2. 总交换空间 \(S_{\text{total}}\)所有启用 swap 设备的总容量,由 swapon -s 可见。
    Stotal=Swap 总字节S_{\text{total}} = \text{Swap 总字节}
  3. 利用率两者相除得到百分比,判断是否需要扩容或调整策略。
    SusedStotal×100%→健康度\frac{S_{\text{used}}}{S_{\text{total}}} \times 100\% \rightarrow \text{健康度}

实战策略:不同厨房的装修方案

同样一套收纳哲学,在不同厨房的装修方案完全不同。

桌面/低配笔记本:物理内存常为 4-8GB,运行浏览器和 IDE 很容易吃紧。推荐方案是大力启用 zRAM:创建一个 1:1 于内存大小的 zRAM 设备,格式化为 swap,并把 swappiness 调到 100 以上,让冷页优先被压缩进内存而不是写盘。这样即使台面满了,系统也只是从压缩柜取东西,不至于卡到无法操作。

云服务器:内存规格通常和价格直挂钩,用户希望把 2GB 或 4GB 的机器压榨出最大价值。但云环境最怕抖动——如果 swap 落在低性能云盘上,频繁换页会让服务延迟雪崩。稳妥策略是:zRAM 作为唯一 swap 设备,或 zRAM 高优先级 + 极小磁盘 swap 兜底,同时把 swappiness 调到 20-60,让内核优先回收文件缓存而不是把匿名页写到慢盘。

容器环境(Kubernetes/Docker):容器有自己的 cgroup 内存限制,宿主机 swap 策略影响复杂。传统做法是禁用 swap,但代价是容器一旦超限立刻被杀。现代做法是把宿主机 zRAM swap 的优先级提高,仅在极端情况兜底,同时为每个容器设置合理的 memory.limit,让“台面”在容器维度就有清晰边界。这样既避免 OOM 杀进程,又不会让一个容器的 swap 抖动拖垮整台宿主。

调优效果要用数据说话——压测前后的对比指标:

从“看出问题”到“管道工式地拧紧每一个螺母”,现在你已经掌握了一套完整的厨房收纳体系——接下来看看这套手艺在真实 AI 工作场景中如何救火。

---

性能变化率=T调优后−T调优前T调优前×100%\displaystyle \text{性能变化率} = \frac{T_{\text{调优后}} - T_{\text{调优前}}}{T_{\text{调优前}}} \times 100\%
负值代表延迟降低、性能提升。记录应用完成一个典型任务(如首次页面渲染、批处理任务)的耗时,在调优前后各测 5 次取中位数,就能量化“这套厨房装修”的真实收益。
  1. 调优前耗时 \(T_{\text{before}}\)未启用 zRAM/swap 优化时完成典型任务的时间,作为基线。
    T调优前=基线耗时T_{\text{调优前}} = \text{基线耗时}
  2. 调优后耗时 \(T_{\text{after}}\)采用新配置后的耗时。
    T调优后=新配置耗时T_{\text{调优后}} = \text{新配置耗时}
  3. 差值百分比用差值除以基线,得到提升比例的客观度量。例如 -20% 意味着快了 20%。
    Tafter−TbeforeTbefore×100%=收益幅度\frac{T_{\text{after}} - T_{\text{before}}}{T_{\text{before}}} \times 100\% = \text{收益幅度}

AI 应用场景

场景一:低配云主机跑 AI 推理服务

阶段:模型推理部署与运维

解决的问题:2GB 内存的云主机想部署一个 300MB 的 NLP 模型。传统方式下,模型权重加载后内存占用飙到 80%,并发请求一多就触发 OOM,Nginx 直接 502。AI 工程师常常被迫升级到 4GB 规格,费用翻倍。

具体案例:在 Ubuntu 云主机上启用 zRAM(1:1 配置,LZ4 算法)并将磁盘 swap 作为兜底,同时把 swappiness 调到 60。实测 2GB 机器稳定承载 50 个并发推理请求,模型权重中的冷层被压缩进 zRAM,访问时解压开销仅增加约 8% 延迟,但服务器月成本省下一半。

场景二:Kubernetes 节点内存水位治理

阶段:分布式训练与资源调度

解决的问题:AI 训练集群的 K8s 节点经常因为 kubelet 或系统守护进程占用内存,导致 Pod 被驱逐(Evicted)或 OOMKilled。传统做法是直接禁用 swap,但宿主机自身的页面缓存波动依然会引发水位线告警。

具体案例:某团队在 16GB 节点上启用 4GB zRAM swap,并将 vm.min_free_kbytes 调高以保留紧急内存池。此后节点内存水位长期稳定在 low 线以下但不触发直接回收,Pod 重启率下降 70%,GPU 利用率提升 12%。

场景三:边缘设备端侧推理的内存压缩

阶段:边缘推理优化

解决的问题:树莓派/嵌入式设备只有 1-4GB 内存,加载 YOLO 或语音模型后几乎没有余量给中间特征张量。传统做法是缩小 batch size 或降分辨率,牺牲推理精度。

具体案例:在运行 Linux 的边缘设备上启用 zRAM(ZSTD 算法,压缩率更高),把推理框架产生的临时匿名页压缩在内存中。实测 YOLOv5s 推理时可同时驻留检测进程和视频缓存,峰值内存占用下降 32%,FPS 仅损失 4%。

Python 演示

python61 行
#!/usr/bin/env python3"""memswap_check.py: check Linux memory pressure and zRAM/Swap status.""" from pathlib import Path  def read_key(path, key):    """Read a key from /proc/meminfo, unit in kB."""    for line in Path(path).read_text().splitlines():        parts = line.split()        if parts[0].rstrip(':') == key:            return int(parts[1])    return 0  def get_swap_info():    """Read /proc/swaps, return (used, total) swap in kB."""    used = total = 0    lines = Path('/proc/swaps').read_text().splitlines()    for line in lines[1:]:        parts = line.split()        total += int(parts[3])        used += int(parts[4])    return used, total  def get_zram_status():    """Read zram0 compression status if available."""    base = Path('/sys/block/zram0')    if not base.exists():        return None, None    orig = int((base / 'orig_data_size').read_text())    comp = int((base / 'compr_data_size').read_text())    return orig, comp  def main():    mem_total = read_key('/proc/meminfo', 'MemTotal')    mem_avail = read_key('/proc/meminfo', 'MemAvailable')    sw_used, sw_total = get_swap_info()    z_orig, z_comp = get_zram_status()     pressure = (mem_total - mem_avail) / mem_total * 100    swap_ratio = (sw_used / sw_total * 100) if sw_total else 0     print('物理内存: %.1fGB, 可用: %.1fGB' % (mem_total / 1024**2, mem_avail / 1024**2))    print('内存压力: %.1f%%' % pressure)    if sw_total:        print('Swap: %.1f/%.1fGB (%.1f%%)' % (sw_used / 1024**2, sw_total / 1024**2, swap_ratio))    if z_orig is not None and z_comp > 0:        print('zRAM: 原始 %.1fMB -> 压缩 %.1fMB, 压缩率 %.1f%%' % (            z_orig / 1024 / 1024, z_comp / 1024 / 1024, (1 - z_comp / z_orig) * 100))     if pressure > 85 or swap_ratio > 60:        print('警告: 内存压力高, 建议扩容或启用/调优zRAM')    else:        print('状态正常')  if __name__ == '__main__':    main()
目的:在 Linux 上一键读取物理内存、Swap 与 zRAM 的真实状态,自动计算内存压力、swap 利用率和压缩率,为调优提供数据决策。
  1. mem_avail = read_key('/proc/meminfo', 'MemAvailable')读取内核官方“真实可用”内存估计,比手动算 free+buff/cache 更准
  2. pressure = (mem_total - mem_avail) / mem_total * 100计算内存压力百分比,压力 >85% 时系统已进入危险区
  3. sw_used, sw_total = get_swap_info()解析 /proc/swaps,获取所有 swap 设备的已用与总量
  4. swap_ratio = (sw_used / sw_total * 100) if sw_total else 0计算 swap 利用率,防止除零
  5. z_orig, z_comp = get_zram_status()从 /sys/block/zram0 读取原始与压缩后的字节数
  6. orig = int((base / 'orig_data_size').read_text())zRAM 的原始数据量是字节单位,读出后需换算成 MiB
  7. if pressure > 85 or swap_ratio > 60:简单的健康度判断阈值:压力过高或 swap 占用过大就告警

常见误区

误区反正内存不够用,把 swap 配成内存的好几倍,问题就解决了。我直接给服务器配了 64G swap,跑大数据再也不 OOM 了,这总该万无一失了吧?

正解swap 只是“地下室”,不能替代台面。当系统频繁从地下室搬进搬出时,磁盘 IO 成为瓶颈,CPU 大量时间花在等待上,吞吐率断崖式下跌。swap 空间够用就好(参考容量规划公式),重点是合理搭配 zRAM、控制内存水位,而不是无限扩大地下室。

为何常见:看到 swap 能缓解 OOM 就以为越大越安全,忽略了磁盘与内存之间数量级的性能差距。

误区把 swappiness 改成 0,系统就永远不会碰 swap 了,性能肯定提升。我看很多人都在传这个技巧。

正解swappiness 是“匿名页与文件缓存的回收偏好旋钮”,不是 swap 的开关。值为 0 只是让内核几乎不主动换出匿名页,但内存极度紧张时仍会换出;在内存分配压力下,直接回收机制依然可能搬匿名页。想彻底不用 swap,得 swapoff,或者用 cgroup 隔离内存。

为何常见:把“偏好旋钮”误解成了“开关”,忽略了内核在生死关头会打破常规预案。

误区zRAM 要压缩解压肯定吃 CPU,现在 NVMe SSD 那么快,直接 swap 到 SSD 不是更好?何必在内存里多做一道手续。

正解LZ4 压缩 4KB 页面只需微秒级,SSD 一次随机 IO 却在几十到几百微秒甚至更高,zRAM 的速度优势依然巨大。而且 zRAM 不占用磁盘带宽,不会跟业务 IO 抢道。实测中,同一负载用 zRAM 比用 SSD swap 的尾延迟低一个数量级。

为何常见:直觉上觉得“压缩=慢”,忽略了实际比较对象是慢得多的磁盘 IO,也低估了现代压缩算法的吞吐能力。

误区free 命令里 buff/cache 那么大,可用内存不就直接等于 free 加上 buff/cache 吗?我看很多教程都这么算。

正解MemAvailable 不是简单加法。一部分 buff/cache 正被进程使用(如共享内存、正在读取的文件),不可回收;还要考虑内核必须保留的水位线 min_free_kbytes。内核提供的 available 字段已经把这些因素算进去,直接用更可靠。

为何常见:早期 Linux 教程的习惯性估算方法流毒太广,而 MemAvailable 是相对较新的统计字段,信息差导致误解。

误区云厂商和社区都说 swap 会导致延迟抖动,那我在云主机上直接把 swap 关掉,再加个 zRAM 也只是浪费 CPU,干脆全都不要。

正解完全无 swap 的机器,一旦内存耗尽就触发 OOM killer 随机杀进程——对线上服务来说,进程被杀死比慢几毫秒致命得多。合理方案是保留小容量 zRAM 作为第一层 swap,并设置适中的 swappiness,让系统有兜底又不至于把慢盘拖进来。

为何常见:把数据库专用集群的“禁用 swap”策略无差别推广到所有云主机,忽略了不同应用的容错成本差异。

面试题

Q1. 系统内存不足时,内核通常优先回收哪一类页面?

单选题 · easy

  • A.进程的匿名页
  • B.文件缓存页← 正确答案
  • C.内核栈页面
  • D.页表本身

答案:B。文件缓存页在磁盘有原始副本,就像干货罐头扔了还能从仓库拿新的,优先丢弃成本最低。匿名页是正在处理的鲜肉,扔了数据就没了,必须换出而不是直接回收。内核栈和页表属于内核核心结构,几乎不可回收。

Q2. Linux 中 swappiness 默认值及其作用方向是?

单选题 · mid

  • A.默认 60,值越大越倾向回收文件缓存
  • B.默认 60,值越大越倾向换出匿名页← 正确答案
  • C.默认 100,值越大越倾向换出匿名页
  • D.默认 30,值越大越倾向保留文件缓存

答案:B。swappiness 默认值是 60。增大它意味着厨师更愿意把食材打包进地下室(换出匿名页),而不是丢弃干货罐头(回收文件缓存)。它调节的是两种回收方式的相对倾向,不是 swap 开关。

Q3. zRAM 技术的核心工作方式是?

单选题 · mid

  • A.把内存页压缩后仍存放在内存中← 正确答案
  • B.把内存页解压后存放到磁盘
  • C.把内存页直接映射到 GPU 显存
  • D.把内存页复制到 CPU 高速缓存

答案:A。zRAM 是在内存里开辟的压缩块设备,如同把青菜抽真空后仍放在厨房台面角落。它不走磁盘,所以比 swap 分区延迟低得多。B 是 swap 的机制,C/D 与 zRAM 无关。

Q4. 关于 swap 分区与 swap 文件,以下说法正确的是?

多选题 · mid

  • A.swap 文件可以按需创建和删除← 正确答案
  • B.swap 分区使用裸设备更直接← 正确答案
  • C.swap 文件的优先级永远低于分区
  • D.多个 swap 设备可设不同优先级← 正确答案

答案:ABD。swap 文件是普通文件,可用 mkswap/swapon 按需启用和移除;swap 分区是裸块设备,少一层文件系统开销。优先级由 pri 参数决定,与文件还是分区无关,可给多设备设不同优先级,priority 值越大越先使用。C 的说法不成立。

Q5. 内存紧张时 kswapd 后台回收与直接回收的区别是?

单选题 · mid

  • A.kswapd 异步运行,直接回收同步阻塞分配者← 正确答案
  • B.kswapd 只回收匿名页不回收文件缓存
  • C.直接回收只换出匿名页不压缩
  • D.二者在功能上完全等价

答案:A。kswapd 像定期巡场的帮工,空闲内存降到 low 水位后在后台异步回收;直接回收是进程分配内存等不及了,自己停下手上活同步清理,延迟显著升高。两者都会按需处理匿名页和文件缓存,功能不等价。

Q6. 在较新的 Linux 内核中,swappiness 的合法取值范围是?

单选题 · hard

  • A.0 到 100
  • B.0 到 120
  • C.0 到 200← 正确答案
  • D.0 到 256

答案:C。早期内核是 0-100,较新内核扩展为 0-200。超过 100 意味着即使有可回收文件缓存,内核也愿意优先把匿名页换出到 swap。这个大范围给了桌面和嵌入式系统更大的调优空间。

Q7. 查看系统当前 swap 使用率最直接命令是?

单选题 · easy

  • A.free -h← 正确答案
  • B.lsblk
  • C.df -h
  • D.uname -a

答案:A。free -h 直接输出内存与 swap 的总量、已用、可用。lsblk 看块设备结构,df 看文件系统用量,uname 看内核版本,都不直接显示 swap 统计。

Q8. 匿名页换出(swap out)过程的正确描述包括?

多选题 · hard

  • A.需要先在 swap 设备上分配槽位← 正确答案
  • B.写回 swap 后页面才能被释放← 正确答案
  • C.换出时页面内容直接丢弃
  • D.换入时总是仍然命中原内存地址

答案:AB。匿名页没有磁盘副本,必须先在 swap 设备分配一个槽位,把内容完整写过去,之后才能释放物理页。若写回前内容丢失,数据就永久损坏,所以不可能直接丢弃。换入后的内存地址是新的,需要更新页表映射,D 错误。

Q9. 将 zRAM 与磁盘 swap 组合使用的推荐方式是?

单选题 · mid

  • A.zRAM 设置高优先级,磁盘 swap 低优先级← 正确答案
  • B.磁盘 swap 高优先级,zRAM 低优先级
  • C.两者不能同时启用
  • D.zRAM 只能用于根文件系统

答案:A。经典组合是 zRAM 做第一层 swap(压缩且在内存中),满了再溢出到磁盘 swap,就像先在台面压缩柜找地方,放不下才去地下室。priority 值大的设备先被使用,所以 zRAM 应设高优先级。

Q10. 以下哪些措施可以有效缓解内存压力?

多选题 · easy

  • A.增加物理内存条← 正确答案
  • B.启用 zRAM 压缩← 正确答案
  • C.合理配置 swap 空间← 正确答案
  • D.把 swappiness 调到极大

答案:ABC。加内存是根治方案;zRAM 把冷页压缩后仍在内存,直接省空间;swap 提供后备容量,避免 OOM。但无限调大 swappiness 只是让系统更频繁换页,持续压力下反而造成抖动,不是无脑有效的缓解手段。