☰
Linux swap机制全解析:从虚拟内存原理到调优实战
2026/10/15 6:06:21 网站建设 项目流程

说句实话,很多人刚接触 Linux 时,第一眼看到swap这个词,脑子里自动把它翻译成“一块被当年内存使用的磁盘空间”。这个说法不算错,但它会误导你对内存和磁盘关系的理解。在我参与过的几个线上项目中,真正出事的那几次,都是因为团队对 swap 的判断停留在这个层面上:要么觉得“没内存就全靠 swap 扛”,要么觉得“swap 慢,直接关掉,反正内存够大”,结果一个在流量高峰期被 OOM 砍了关键进程,另一个在冷启动时被 I/O 拖到服务彻底卡死。

swap 的准确定义,应该从内核视角去看:它是一种基于存储设备的虚拟内存扩展区域。内核在物理内存出现回收压力之前,会先把“冷”的内存页换出去,腾出空间给真正活跃的数据页,而在进程真正要访问那块内存时,再把数据换回来。这套机制给系统加了一道缓冲,让你在不动硬件的情况下多出临时内存空间,也让系统可以在极端情况下不至于立刻崩溃。但缓冲用得好,能救命;用不好,会让磁盘 I/O 变成新的瓶颈。

这篇文章围绕 Linux 的交换机制展开,从分页原理讲到分区和文件怎么选,从内核参数怎么调讲到线上怎么监控、踩过哪些坑。适合刚接触服务器运维的初学者,也适合已经在生产环境里被 swap 问题折磨过、想系统性补全这块知识的人。

1. 交换分区是什么:Linux 内存管理中的“第二层缓冲”

1.1 分页机制:swap 背后最基础的事实

要理解 swap,先要接受一个 Linux 内核的底层事实:内存不是按“字节”孤立管理的,而是按“页”来管理的。在 x86_64 平台上,默认内存页大小通常是 4KB,内核把物理内存划分为无数个 4KB 的页,同时给每个进程维护一套页表,页表负责把“虚拟地址”映射到“物理地址”。进程自己感知不到物理地址的存在,它看到的是一段连续虚拟地址空间,而这种间接映射关系,正是 swap 能存在的前提。

当内存里有大量页时,内核需要决定哪些页是“热”的(频繁访问,必须留在物理内存),哪些是“冷”的(很少访问,可以挪到磁盘)。Linux 内核用一套近似 LRU 的链表结构来维护页面访问热度:活跃链表里的页最近被访问过,非活跃链表里的页则是有资格被回收或者被换出的候选对象。当系统可用内存不足、分配请求到来时,内核就会触发内存回收,从非活跃链表里挑页来释放。

理解分页之后会发现,swap 并不是一个独立的“空间模拟器”,而是内存回收机制的一部分:当回收的对象是文件缓存页(比如读文件留下的 page cache),那直接丢弃或写回磁盘即可,这就是“丢页”;当回收的对象是匿名内存页(比如进程用 malloc 申请的堆内存数据),因为没有磁盘上的原始来源,内核就必须先把这些数据写到专门的 swap 区域,否则下次进程访问时就找不回来了。这个过程就是“换出”,也就是 swap out。

1.2 swap 的实际收益和代价

swap 存在的目的,不是为了让你“假装内存很大”,而是为了解决三类现实问题。

第一,短时内存峰值。进程的内存需求不是恒定的,可能在某个瞬间比平时多出 1GB,比如大批量导入数据、压缩大型日志文件。如果完全没有 swap,内核只能触发 OOM Killer,从系统里挑一个进程杀掉来释放内存。有了 swap,系统可以把后台的一堆冷内存页暂时挪到磁盘,把物理内存腾出来给正在涨内存的进程,等峰值过去再把冷页换回内存。这一来一回虽然慢,但至少服务没有断。

第二,冷内存回收。Linux 里的文件缓存 page cache 本来就可以通过丢弃来释放内存,但如果系统里全是大段匿名内存,比如十几个后台进程的堆都闲着,内核回收起来就比较麻烦。有 swap 之后,内核可以把长期不访问的匿名页换出,这种操作能让物理内存更高效服务于真正的热点数据,而不是被“沉睡”的进程占用。

第三,休眠功能。桌面系统或者笔记本执行 suspend-to-disk 时,系统需要把整个内存镜像写到磁盘。这个镜像存储一般就放在 swap 空间里,如果没有 swap,休眠功能无法工作。

但是,任何收益都有代价。磁盘和内存的速度差距是数量级的。内存随机访问延迟在纳秒级,NVMe SSD 在微秒到几十微秒量级,传统机械硬盘在毫秒量级。如果系统频繁发生 swap in/swap out,也就是“交换风暴”,所有业务进程都会卡在磁盘等待上,甚至比直接内存不足还难受。所以在设计和调优时,核心不是“要不要 swap”,而是“swap 应当被放置在何时介入、介入多少”的问题。

1.3 哪些场景必须保留 swap,哪些建议关闭

根据我实际接触过的项目,swap 的使用策略大致可以分成三类。

典型场景swap 建议原因
桌面系统 / 开发机开启,大小 2~8GB浏览器、IDE、编译任务经常出现瞬间内存峰值,且支持休眠
具备前文大量闲置进程的服务器开启,但降低 swappiness防止闲置进程占用内存,给热点业务让路
高并发、低延迟服务少量或关闭避免任何可能的磁盘交换抖动,配合内存监控兜底
独立数据库节点视版本和缓存策略而定数据库通常自己管理缓存,但完全关闭要谨慎
大规模容器宿主开启运维时内存因为各隔离环境分配可导致整体资源紧张

这里我给一个个人观点:不建议在服务器上彻底关闭 swap。很多人认为“内存足够大就不需要 swap”,但内存足够大并不代表内存分配不会发生瞬时失败,也不代表某个进程不会因为 bug 突然疯涨内存。swap 更像一道保险丝,它写满后系统会慢下来,但至少给你留出登录排查、调整配置的空间。完全关闭 swap 的机器一旦遇到突发内存压,就只剩 OOM 这一条路可走。

2. 交换分区还是交换文件:两种实现方案怎么选

2.1 两种方案的本质差异

swap 在 Linux 里有两种载体:一种是独立的交换分区,另一种是普通文件系统里的交换文件。两者的功能完全一致,最终都是被内核当作按页读写的块设备来用,但在管理方式、灵活性和容错性上差别很大。

交换分区是传统安装方式。安装 Linux 系统时,磁盘里专门划一个分区,不挂载到任何目录,格式化时使用mkswap把它变成交换区。它的优点是非常稳定,不依赖上层文件系统逻辑,断电后不需要检查文件系统,直接就是裸设备。缺点是分区大小在创建时就定死了,后期想扩容很麻烦,尤其是磁盘分区表已经排满的情况下,调整分区布局往往就是一场小手术。

交换文件则更像一个普通文件,放在 ext4、xfs 之类的文件系统里。现代 Linux 内核完全支持映射一个文件区域当作 swap。它的最大好处是灵活:大小随意调整,创建和删除都是普通文件操作,不需要重新分区。很多云服务器和虚拟机的初始化脚本都喜欢用交换文件,因为一条命令就能完成配置。缺点是它受文件系统影响,在某些文件系统(特别是 btrfs)上使用受限,而且如果文件系统出问题,swap 文件也跟着受影响。

用一张表整理关键对比会更直观:

维度交换分区交换文件
灵活性低,扩容要改分区表高,随时创建、删除、调整
稳定性高,绕开文件系统逻辑受文件系统状态影响
性能两者几乎一致,取决于底层设备两者几乎一致,取决于底层设备
休眠支持支持部分发行版/存储驱动下支持
运维复杂度分区操作有风险只需文件操作,心智负担低

我做项目时的一般习惯是:物理机安装系统,直接用交换分区,省心;云服务器或需要频繁调整磁盘布局的环境,优先交换文件,灵活。

2.2 交换文件的创建全过程

假设要给一台 Ubuntu 服务器增加 4GB 交换文件,完整流程如下。

第一步,创建文件。最稳妥的方式是用dd填充零。虽然fallocate更快,但有些文件系统(比如 btrfs)或某些特性场景下,fallocate分配的空间可能出现“文件空洞”或特殊块对齐问题,导致swapon直接报错或性能异常。所以我的习惯是:为了数据可靠性优先用dd:

dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress

创建完看一眼大小,然后立刻限制权限。swap 文件里保存的是进程私有内存的镜像,如果权限是 644,别的用户理论上能读到内容,这绝对要避免:

ls -lh /swapfile chmod 600 /swapfile

第二步,格式化成 swap 区域:

mkswap /swapfile

命令会输出 UUID 信息,类似UUID=xxxx。这一步本质是给文件写上 swap 的超级块标记,让内核能正确识别它。

第三步,激活 swap:

swapon /swapfile

然后用两条命令验证是否成功:

swapon --show free -h

swapon --show输出里能看到 NAME 是/swapfile,TYPE 是file;free -h里 Swap 一行的 used 和 total 都有数据,说明内核已经能使用它了。

这套流程我在一个模拟项目的清理反复验证过,从创建到生效不超过半分钟,非常适合现场应急操作。

2.3 交换分区的创建过程和 LVM 方案

交换分区流程也不复杂。假设用fdisk在 /dev/sdb 上建一个 4GB 分区,可以这样:

fdisk /dev/sdb

进入 fdisk 交互界面后,用n创建新分区,默认主分区编号,起始扇区直接回车,结束扇区输入+4G,然后w写入退出。

接下来两步和文件方式几乎一样:

mkswap /dev/sdb1 swapon /dev/sdb1

如果是 LVM 环境,我更推荐用 LVM 逻辑卷来承载 swap。这样之后扩容趋势只需lvextend和mkswap一条命令,不用动分区表:

lvcreate -L 4G -n lv_swap vol_group mkswap /dev/mapper/vol_group-lv_swap swapon /dev/mapper/vol_group-lv_swap

LVM 的优势是快照和管理灵活,生产机上的 swap 用 LVM 承载,遇到物理磁盘迁就、扩容一些冷门怪问题时,比直接改分区表安全得多。

2.4 持久化挂载:别让重启之后全部归零

不管是文件还是分区,swapon只是临时启用,重启后就会失效。想要开机自动启用,必须写入/etc/fstab。

交换文件的写法:

/swapfile none swap defaults 0 0

交换分区或 LVM 卷,推荐用 UUID 而不是设备名。设备名如/dev/sda2在更换磁盘、调整盘序后可能变化,UUID 是稳定的身份标识。用blkid查询分区 UUID 后,fstab 写入如下内容:

UUID=xxxx-xxxx-xxxx none swap defaults 0 0

这里有个小细节:fstab 中设置的 UUID 是格式化后的文件系统或者 swap 区域的 UUID,不是分区表里的 UUID。写错 UUID 会导致开机进入维护模式或 start job 等待超时,这是很常见的配置错误。

我一般还会在 fstab 里为多块 swap 设备设置优先级:

UUID=xxx none swap defaults,pri=100 0 0 /dev/swapfile none swap defaults,pri=50 0 0

优先级高的 swap 先被使用,这样可以控制“好盘”优先承担交换负载,坏盘当备用。内核在没有显式设置优先级时,会让同一个 swap 设备之间的使用更均衡化。

3. 内核如何调度 swap:关键参数与调优逻辑

3.1 swappiness 到底在控制什么

说起 swap 调优,绕不开的就是vm.swappiness。大家对这个参数有很多流传很广的理解,但其中有明显偏差:它不是“当内存使用率达到多少百分比就用 swap”。它的真实含义是:当内核需要回收内存页时,它偏向于回收“匿名页”还是“文件缓存页”的程度。

参数范围默认是 0 到 100,多数发行版默认值是 60。值越大,内核在回收页时更愿意把匿名内存页 swap 出去;值越小,内核越倾向先回收文件缓存、留下匿名内存页。如果你把 swappiness 设成 0,不是说 swap 就不用了,而是在页回收时尽量不主动换出匿名页,只有在内存极其紧张、文件缓存也无法满足回收需求时,才不得不做 swap。

还有一个常见的直观误区:认为“内存还剩 80% 时系统就留着 swap”。实际上,触发 swap 的时机与可用内存比例有一定关系,但更核心是看页的活跃度。一个进程可能占用了 60% 内存,却有一大半是长期不访问的冷页。内核通过 LRU 表发现这些冷页时,即便系统整体内存还有余量,也会在回收优先级把其中部分冷页换出,为文件缓存或其他活跃页留出充足空间。这是为什么很多服务器上明明内存看着没用满,swap 却已经出现 used。

调动态参数的命令很简单:

sysctl vm.swappiness=10

这只是临时生效。要持久化,写入/etc/sysctl.conf:

vm.swappiness=10

然后执行sysctl -p使其生效。

对于桌面系统,我一般调到 30~60;对于延迟敏感的数据库和推荐减少磁盘交换的服务器,会调到10 或更低。要注意,低 swappiness 配合高匿名内存占用,反而可能导致突然 OOM,因为可回收的对象太少了。不能只改一个参数就算完,要考虑整个系统的内存池结构。

3.2 内存回收和水位的联动

内核不用把内存耗尽才回收页,它依靠一套“水位线”机制。

Linux 内存管理把可用内存划分成几个阈值:高于 high 水位,内存充足;介于 high 和 low 水位之间,软回收逐步启动;降到 low 或 min 水位时,内核会在分配路径上执行直接回收,也就是请求内存的进程在分配内存的同时要参与回收,这是卡顿的来源。后台回收线程 kswapd 的工作,则是尽量让空闲内存维持在水位之上。

具体到 swap 调度,有几个参数值得关注。

vm.vfs_cache_pressure控制内核回收目录项和 inode 缓存的热度。值越高,表示内核越激进回收文件系统缓存;值越低,文件系统缓存越容易留在内存里。如果这个值很大,就会使得主导内存回收压力转移到匿名页,反而可能变相增加 swap 使用。

vm.page-cluster控制一次 swap 读入内存的页数量。默认值是 3,意思是读入 2 的 3 次方,也就是 8 个页。这个参数对机械硬盘影响大,因为顺序读取连续 8 页比每次读 1 页效率更高,但 SSD 时代这个参数稍显冗余,也没必要乱改。

vm.min_free_kbytes决定内存管理保留的最小空闲内存量,它直接影响 low 和 high 水位线的高度。一些内存大量、频繁跳动的系统上,适当调大这个值,能避免内核在非常紧的水位触发上直接回收而带来长延迟。但调太大会浪费内存,必须根据内存总量和分配行为小心权衡。

3.3 我实际用过的两类调优方案

我通常给不同项目准备两套基准方案,再根据监控数据微调。

第一种,偏向低延迟的 Web 服务或容器宿主:

vm.swappiness=10 vm.vfs_cache_pressure=50 vm.min_free_kbytes=128000

含义很明确:尽量保留匿名页,减轻文件缓存回收压力,同时保留一部分物理空闲内存给突发分配,减少直接回收路径。这套方案下 swap 使用率通常很低,但关键时刻还是能兜底。

第二种,数据节点或批量任务节点,内存用途单一,业务自己管理缓存:

vm.swappiness=10 vm.vfs_cache_pressure=100 vm.dirty_ratio=5 vm.dirty_background_ratio=2

这里把脏页写回阈值压低,避免大量脏页堆积导致突然写盘风暴,同时让文件缓存更容易回收,避免 swap 被当作“逃生舱”频繁使用。

需要强调,这些参数不能照搬。不同发行版、不同内核版本、不同存储介质对参数的响应不一样。真正靠谱的做法是:先记录业务基线,再改参数,然后用压测或者高峰流量验证。只改参数不压测,不仅不会优化,还很可能把原本稳定的系统搞出奇奇怪怪的延迟问题。

4. 监控、排查和一线经验

4.1 日常监控 swap 的几个命令

监控 swap 不需要多复杂的工具,几个系统自带命令足够看出问题。

free -h是最直观的状态入口。输出里的 Swap 行能看到总大小、已用、可用。配合 Mem 行看,能判断磁盘交换空间占总内存多大比例。

swapon --show看所有已启用的 swap 设备,包括类型、大小、优先级和实际使用量。排查多 swap 硬件问题时,这个命令比 free 更详细。

vmstat 1 5里有两个关键字段:si(swap in)和 so(swap out),每秒多少数据被换入换出。如果 so 持续偏高,说明系统正在把内存页持续写盘,这就是交换风暴的前兆。si 持续偏高,说明有进程正在频繁访问已经被换出的页,对业务来说就是“内存不再有随机访问的幸福感”。

cat /proc/meminfo里也能看到 SwapTotal、SwapFree、SwapCached 等字段。SwapCached 表示那些已被换出、但最近又被读回内存的页,很多这类缓存页其实是可以快速回收的。这个数字大,说明 swap 和内存之间正在高频互动。

如果项目监控系统里有类似于记录进程 RSS、Swap 占用、IO 延迟的面板,我也建议把交换指标作为核心告警项。我的告警阈值经验是:持续 5 分钟 so 超过 500MB/s 或 si 超过 500MB/s,就立即触发告警。这个阈值不是通用值,但要告诉读者:看交换趋势,比看瞬时快照重要得多。

4.2 常见问题速查表

我把线上遇到过的 swap 相关问题整理成一张表,方便快速对号入座。

问题现象可能原因排查与解决办法
swap used 很高但物理内存尚有空余历史峰值期把冷页换出后未换回;swappiness 过大用 vmstat 观察 si/so 趋势;调整 swappiness 到 10 以下;冷页会在后续访问时逐步换回
swap 写满,系统卡死某个进程内存泄漏或突发猛涨优先定位内存大户top -o %MEM;查 cgroup 限制;考虑临时扩容或快速重启问题服务
swapoff 执行失败内存不够容纳 swap 中的数据量先free -h看余量;分批关闭或先清理无用进程;不要强行 kill 关键进程
重启后 swap 没自动挂载fstab 的 UUID 或设备名写错blkid核对 UUID;swapon -a测试;系统 journal 查看等待超时日志
磁盘 I/O 蹭蹭涨但业务没申请磁盘swap 正在频繁换出,或脏页写回用 iotop/iostat 验证写盘进程;检查 si/so;调整 swappiness 和 dirty_ratio
swap 使用同步波动频繁冷热页翻转、多 swap 设备优先级不均检查各 swap 设备 pri 差异;用 SwapCached 观察;必要时只用单块高性能 swap 设备

这张表是我在一段长期维护中沉淀出来的,实际排查时还要结合业务上下文,但看这几条大方向基本不会跑偏。

4.3 三个踩坑案例,写在最后比较有用

第一个案例来自一个线上数据库节点。节点内存 64GB,当初安装时默认带了 8GB swap,团队图省事没有调整。某次业务压测突然出现大量慢查询,排查了一圈发现数据库负载并不高,反而是vmstat里的 si/so 疯狂上涨。原因是一个统计任务把数据库进程内存推到峰值,内核把大量冷内存页换出,而数据库在下一个小时不断访问这些页,造成持续换入换出。这个案例给我的教训是:对延迟敏感的服务,swap 可以做,但一定要压低 swappiness,并且给关键进程设好内存使用上限,避免峰值冲击。

第二个案例来自一个桌面系统的模拟环境。系统内存 16GB,默认 swappiness 60,平时没感觉。某次同时开了 IDE、虚拟机、几十个浏览器标签页,物理内存还没耗尽,系统就开始把页面疯狂写盘。结果 UI 响应变得极其卡顿,鼠标移动都像慢动作,最后用了物理重启。这个案例让我意识到,桌面系统同样不能放任默认参数,根据自身磁盘介质、内存行为调到 10~30 之间,安全性会提升很多。另外,桌面机上还有一个隐藏问题:所有平台的缓存系统、后台组件密集时,swap 文件所在磁盘如果是一块老旧机械盘,卡顿会被放大数倍。

第三个案例来自把 swap 完全关闭的某台应用服务器。当时运维认为该服务主要是内存型缓存,内存绝对够用,于是做容量评估时删掉了所有 swap。结果服务运行到第四天,因为一个第三方库存在内存泄漏,内存被慢慢吃掉,线上某进程突然被 OOM 杀掉,整个应用负载全部退化,恢复耗时好几个小时。如果当时保留一个 2GB 的 swap 文件,哪怕进程在 2GB 峰值里挣扎变慢,至少能有时间安全摘流、重启或者定位。这个案例是教训,也是我想对坚持“定能关 swap”的读者说的:你可以调小,可以降低 swappiness,但尽量不要让系统完全失去最后那层防护。

最后分享一个我自己的习惯:我维护的每台机器都会在 /var/log 里留一个简单的巡检脚本计划任务,每隔几分钟做一次类似free与sysctl vm.swappiness的采集,连续记录交换使用趋势。等待系统真正出问题才去看现状是常态,但提前看到趋势曲线并想在源头处理,遇到的紧急情况会少很多。

swap 不是银弹,也不是必须避免的洪水猛兽。它更像一扇安全门:平时不该总走,但门上绝不能没有锁。结合业务确认内存特征,设置合理的大小和参数,保持监控告警,这个老机制依然能稳定妥当地陪在系统身边。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询