给一台跑了半年的服务器关掉 Swap 分区,本以为能少点磁盘 IO、让服务响应更快,结果第二天早上监控告警:几个 Java 进程被内核直接干掉了。这件事让我把 Linux 虚拟内存这套机制从头到尾又捋了一遍。Swap 分区(也就是常说的虚拟内存)在 Linux 上从来不是"内存不够时的备胎"这么简单,它牵扯到页回收策略、内存水位线、OOM Killer 的判定逻辑,甚至会影响你开机能不能进系统。这篇内容我打算把"禁用、添加、修改 Swap"这三件事拆开讲清楚,包括查看现状的正确命令、分区/逻辑卷/swapfile 三种方案怎么选、fstab 和 systemd 到底谁在管这件事,以及我自己踩过的几个比较难受的坑。适合刚接手 Linux 运维的同学,也适合已经会敲mkswap但不太清楚背后逻辑的老手对照着检查一遍。
1. 别急着敲 swapoff:先弄明白 swap 在这台机器上干的什么活
很多人对 swap 的第一印象是"内存用完了才轮到它",这个理解会直接导致错误操作。真实的机制是:内核有一批不常访问的内存页(匿名页,比如进程堆栈、堆数据),与其一直占着物理内存,不如把它们换出到磁盘上,把物理内存腾给更热的页和页缓存。这个过程叫页回收,swap 就是匿名页的回收目的地。注意关键词是"不常访问",不是"内存不够"。也就是说,一台内存看起来还很宽裕的机器,正常情况下也会有几十到几百 MB 的 swap 占用,这是完全健康的状态。
1.1 swap 是页回收的缓冲池,不是 OOM 的开关
内核在做内存回收时有个水位线概念:free 内存低于 low 水位就开始轻量回收,低于 min 水位才会触发更激进的动作。swap 存在的意义是让回收器多一个选择——没有 swap 的机器,匿名页无法换出,回收器只能去砍页缓存(文件页),砍到最后没得砍了,就直接触发 OOM Killer。有 swap 的机器,匿名页可以换出去,页缓存得以保留,文件读写性能不会因为一次内存峰值就崩掉。
这也是为什么"关掉 swap 提速"这个说法在大多数场景下是错的。你删掉 swap,省下的是那点磁盘 IO,代价是页缓存更早被回收,磁盘随机读变多,极端情况下进程直接被 kill。真正该关 swap 的场景其实很窄,后面第 5 节我会具体列出来。
1.2 什么情况下该减 swap,什么情况下必须加
我的判断习惯是先看两个指标:si/so(swap in/out 速率)和pgmajfault(主缺页次数)。如果 so 长期大于 0 且机器明显卡顿,说明 swap 设备本身太慢(比如机械盘、网络存储),这时候要做的是换设备或者上 zram,而不是无脑扩容。如果内存确实吃紧、swap 也没剩多少、dmesg 里已经开始出现 OOM 日志,那就该加了。
反过来,以下几种情况我会主动考虑减小甚至禁用 swap:
- 数据库这类对延迟极度敏感的服务,且机器内存足够,宁可让它 OOM 也不愿意让它偷偷换页导致查询抖动;
- 容器宿主机上跑 Kubernetes,节点层面禁用 swap 是 kubelet 的默认要求;
- 嵌入式设备用的是 eMMC/SD 卡,频繁写 swap 会加速闪存磨损。
2. 摸清现状:三条命令看清 swap 的容量、位置和真实压力
动手之前必须先把现状摸清楚。我发现不少同学只会敲free -h,看到 Swap 那一行是 0 就以为没开 swap,其实未必。
2.1 free、swapon、/proc/swaps 看到的东西并不完全一样
这三者看到的数据源是一致的,但呈现粒度不同:
| 命令 | 能看到什么 | 适用场景 |
|---|---|---|
free -h | 总容量、已用、可用,不带设备信息 | 快速判断有没有 swap、压力大不大 |
swapon --show | 每个 swap 设备的路径、类型、大小、已用、优先级 | 定位 swap 挂在哪个分区或文件上 |
cat /proc/swaps | 同上,但格式更原始,脚本里更好解析 | 写自动化脚本时用 |
lsblk -f | 块设备树的文件系统类型,能看出哪个分区标了 swap | 确认分区是否真的被 mkswap 过 |
free -h里的 used 计算方式容易让人误解,它把 tmpfs、共享内存等也算进去了,所以别拿 used 当唯一依据。更可靠的是看/proc/meminfo里的 SwapTotal 和 SwapFree,再配合vmstat 1 5观察一段时间内的 si/so。注意vmstat第一行是自开机以来的平均值,一定要看后面的实时行,我见过有人拿第一行数据下结论,结果完全判断反了。
2.2 判断 swap 是"假忙"还是"真扛不住"
一个实用的经验值:如果vmstat里 si/so 持续在几十 KB/s 以内,同时pgmajfault不高,说明只是正常的页回收,不用管。如果 si/so 稳定在几 MB/s 以上,同时r(运行队列)和b(阻塞队列)都在涨,那就是真扛不住了,得从内存容量或 swap 设备性能两个方向查。
还要看cat /proc/vmstat | grep -E "pswpin|pswpout",这两个是累计值,隔几十秒采样两次做差值,比看瞬时值靠谱。
提示:云主机上的 swap 可能是块网络盘(比如某些弹性块存储),IOPS 很低。这种设备上 si/so 一高,整机响应就会断崖式下跌,比没有 swap 还难受。
2.3 分清 swap 背后挂的是分区、逻辑卷还是文件
这一步决定了你后面用哪套操作流程。判断方式很简单:
swapon --show lsblk -f blkid | grep -i swap如果输出的是/dev/sda2这种,就是分区或逻辑卷(LVM 下会是/dev/mapper/vg0-swap)。如果是/swapfile、/swap.img这种路径,那就是文件形式。Ubuntu 从 18.04 开始默认用/swap.img(早期是/swapfile),Debian 和大多数 Kali 的默认安装还是给一个 swap 分区。容器里的 swap 情况更特殊,通常直接继承宿主机。
3. 加 swap:分区、逻辑卷、swapfile 三条路怎么选
三条路都能用,区别在于灵活性和操作成本。
3.1 swap 分区的完整创建流程
这是最传统的做法,性能最好(没有文件系统层的开销),缺点是后期调整麻烦。
# 假设新加了块盘 /dev/sdb,先看看现状 lsblk /dev/sdb # 用 fdisk 建分区 fdisk /dev/sdb # n -> 新建分区 # p -> 主分区,选默认起始扇区 # 大小按需填,比如 +4G # t -> 改类型,输入 19(Linux swap)或 82(旧编号) # w -> 写入 # 让内核重新读取分区表 partprobe /dev/sdb # 或者 partx -u /dev/sdb # 格式化并启用 mkswap -L SWAP /dev/sdb1 swapon /dev/sdb1 # 验证 swapon --show这里有个细节经常被忽略:fdisk写完分区表之后必须让内核重新读一次,否则/dev/sdb1这个设备节点可能根本不存在。partprobe在分区正在被使用的盘上会失败,这时候用partx -u更稳。另外mkswap会覆盖分区上的原有数据,操作前一定要blkid确认这个设备上没有别的东西。
如果要指定优先级(多块 swap 设备时控制使用顺序),加-p参数:
swapon -p 10 /dev/sdb1 swapon -p 5 /dev/sdc1数值越大优先级越高,内核会优先用高优先级的设备。SSD 和机械盘混用时这个参数很有用。
3.2 LVM 环境下扩 swap 的顺序不能反
LVM 的好处是理论上可以动态扩,但 swap 逻辑卷的扩容有个卡点:mkswap会重写整个卷头,所以必须先swapoff。
# 1. 先关掉这个 swap 卷 swapoff /dev/vg0/swap # 2. 扩逻辑卷 lvextend -L +4G /dev/vg0/swap # 3. 重新格式化(注意会清空原有 swap 数据,但 swap 数据本来就是临时的,不影响) mkswap /dev/vg0/swap # 4. 重新启用 swapon /dev/vg0/swap第 1 步的swapoff会把已换出的页全部读回内存,如果这台机器内存已经紧张、swap 里存了好几个 G,这一步有可能直接触发 OOM。所以扩 swap 的正确姿势是:先确认内存余量大于 swap 已用量,再操作;如果不行,就先临时加一个 swapfile 兜底,把内存水位降下来再动原卷。
3.3 swapfile 方案:fallocate 和 dd 到底选哪个
swapfile 的最大优势是灵活——加一个文件就行,不用碰分区表。但它有个非常经典的坑:
# 看似没问题的写法 fallocate -l 4G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile # 报错:swapon: /swapfile: swapon failed: Invalid argument原因是fallocate在 ext4 上分配的是"未写入的区段"(unwritten extents),文件在磁盘上没有真正分配连续的块,swap 子系统不接受这样的文件。解决办法是用dd老老实实写:
dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress chmod 600 /swapfile mkswap /swapfile swapon /swapfileXFS 上fallocate反而没问题,所以这个坑只在部分文件系统上出现,很容易让人以为是自己命令敲错了。我个人的做法是统一用dd,慢一点但一定不会出错。另外chmod 600这步绝对不能省,swap 文件权限过宽时swapon会直接拒绝加载,这是出于安全考虑——swap 里可能残留进程内存数据。
如果要"转移虚拟内存",比如把 swapfile 从系统盘挪到数据盘:
swapoff /swapfile # 改 fstab 里的路径,或者删掉旧的换新的 rm /swapfile dd if=/dev/zero of=/data/swapfile bs=1M count=4096 status=progress chmod 600 /data/swapfile mkswap /data/swapfile swapon /data/swapfile3.4 写进 fstab 才算真正生效
前面所有操作都是临时的,重启就没了。必须写进/etc/fstab:
# 分区或逻辑卷用 UUID 更稳 UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx none swap sw 0 0 # swapfile 直接写路径 /swapfile none swap sw 0 0 # 优先级和 swappiness 也可以写在挂载选项里 UUID=xxxx none swap sw,pri=10 0 0这里我强烈建议两点:一是用blkid拿到的 UUID 而不是设备路径,因为设备名可能因为插拔顺序变化;二是加nofail选项,写成sw,pri=10,nofail。
注意:fstab 里写错一行,重启后系统可能直接进 emergency mode。swap 是可选资源,加了 nofail 之后即使这块盘没挂上,系统也能正常起来。
4. 改大小之前,先算清楚到底要多少
swap 该给多大,这个问题没有标准答案,但有几个业界常用的参考口径。
4.1 容量估算的几种口径
| 内存大小 | 传统经验值(等于内存) | 有休眠需求 | 现代实践(8G 以上) |
|---|---|---|---|
| ≤ 2G | 2 × 内存 | 2 × 内存 | 2G |
| 2G ~ 8G | 等于内存 | 等于内存 | 2G ~ 4G |
| 8G ~ 64G | 0.5 × 内存 | 1.5 × 内存 | 4G ~ 8G |
| > 64G | 4G ~ 8G | 不建议休眠 | 4G |
核心逻辑是:如果要用休眠(hibernate),swap 必须能装下全部物理内存内容,这个没得商量。如果不要休眠,那 swap 的作用只是给页回收器留个缓冲区,8G 以上内存的机器给 2~4G 基本够用,给太多反而浪费磁盘。
云主机的情况要单独考虑。很多云厂商的镜像默认给 2G 或者干脆不给 swap,这时候你需要判断业务内存使用是否平稳。如果是突发的批处理任务,加个 4G swapfile 能有效防止偶发 OOM。
4.2 调整已有 swap 的三条路径
- 扩容:分区用
fdisk删了重建(数据无价值,直接重做),LV 用lvextend+mkswap,swapfile 直接建个更大的文件替换。 - 缩容:没有在线缩容的说法,只能
swapoff后重建。swapfile 直接删了重建更简单。 - 重建:先
swapoff所有 swap,清理 fstab,然后按第 3 节的流程重来。
缩容时最容易忽略的是/etc/fstab。删了 swap 分区但忘了删 fstab 那一行,下次开机启动时 systemd 会尝试挂载失败,如果没加 nofail 就会卡在启动流程里,需要进单用户模式或者用救援盘修。
4.3 swappiness 和 vfs_cache_pressure:比容量更影响体感
vm.swappiness控制内核在回收内存时,有多倾向于换出匿名页(swap)而不是回收文件页(page cache)。默认值是 60,意思是两类回收大致平衡。
# 临时调整 sysctl vm.swappiness=10 # 永久生效 echo 'vm.swappiness = 10' > /etc/sysctl.d/99-swap.conf sysctl -p /etc/sysctl.d/99-swap.conf经验值参考:
- 桌面机、交互式应用:10~30,减少无谓的换页,响应更跟手;
- 数据库、缓存型服务:1~10,尽量保住热内存;
- 批处理、内存波动大的机器:可以保留 60 或用 100,让内核大胆换页,用磁盘换内存空间。
另一个参数vm.vfs_cache_pressure控制内核回收 dentry 和 inode 缓存的力度,默认 100。如果你有大量小文件读写(比如代码仓库、邮件服务器),把它调到 50 左右可以让目录缓存保留更久,文件遍历速度明显变快。这两个参数配合调整,效果通常比单纯加 swap 容量好得多。
5. 禁用与移除 swap:顺序错了会很难受
禁用 swap 的命令只有一句swapoff,但用错顺序会导致服务雪崩。
5.1 swapoff 的正确姿势与内存回填风险
# 关闭单个设备 swapoff /dev/sdb1 # 关闭所有 swapoff -a执行swapoff时,内核要把该设备上的所有已换出页重新读回物理内存。如果 swap 里存了 6G,而当前 free 内存只有 3G,那么剩余部分只能靠回收页缓存来腾地方,整个过程会让机器 IO 飙升、响应变慢,最坏情况直接 OOM。所以执行前一定要先看一眼:
grep -E "SwapTotal|SwapFree|MemAvailable" /proc/meminfoMemAvailable减去SwapTotal - SwapFree还有余量才安全。如果不够,先关掉几个不重要的服务腾内存,或者先加一块临时 swap。
5.2 彻底删除:三个地方都要清干净
只敲swapoff是不够的,重启还会回来。完整的清理链路:
- 运行时:
swapoff -a - 开机自启:编辑
/etc/fstab,注释掉或删除对应行;如果用的是独立 systemd unit,systemctl disable xxx.swap - 磁盘层面:swapfile 直接
rm;分区用fdisk删除后partprobe;LV 用lvremove - 可选:如果是整块盘专门做 swap,
wipefs -a /dev/sdb1抹掉签名,避免被其他工具误识别
清理完之后验证:
swapon --show # 应该没有输出 systemctl --type=swap # 应该没有 active 的 swap unit cat /proc/swaps # 应该只剩表头systemd 会在/run/systemd/generator.late/下根据 fstab 自动生成.swapunit,所以改完 fstab 后如果不想重启,可以执行systemctl daemon-reload && systemctl stop xxx.swap来同步状态。
5.3 用 zram 或 zswap 替代传统 swap
如果你的目的是"减少磁盘 IO 又要防 OOM",现代做法是上压缩内存。这跟 Windows 上的内存压缩是同一类思路,只是 Linux 侧有两条路线:
- zram:在内存里划一块区域当块设备,写进去的数据自动压缩,压缩比通常能到 2~3 倍。相当于用 1G 内存换来 3G 的 swap 空间,而且完全没有磁盘 IO。
- zswap:一个前端压缩缓存,换出的页先压缩存在内存里,实在压不下才写到真正的 swap 设备。它需要你有一个真实的 swap 后端。
zram 的配置大致是这样:
modprobe zram # 创建一个 4G 的 zram 设备 zramctl --find --size 4G --algorithm zstd # 假设输出是 /dev/zram0 mkswap /dev/zram0 swapon -p 100 /dev/zram0在 Fedora、Arch 这类发行版上,直接用zram-generator或systemd-zram-setup@zram0.service更省事,写个配置文件就自动管理了。嵌入式设备和小内存 VPS 上,zram 的收益非常明显——我有一台 1G 内存的机器,上了 zram 之后编译小项目再也没被 OOM 打断过。
6. 几个真实踩坑现场的排查链路
前面讲的都是"正常流程",但真实环境里的问题基本都是流程之外的东西。
6.1 fstab 写错导致开不了机
现象:重启后卡在A start job is running for /dev/disk/by-uuid/xxxx,等 90 秒后进入 emergency mode。
排查顺序:
- 在 emergency mode 里先看
journalctl -xb | grep -i swap,找到具体报错; blkid确认 UUID 对不对,很多时候是复制粘贴时少了一位;- 检查设备是否存在:
lsblk,如果这块盘根本没插上,那就属于 nofail 该管的场景; - 临时修复:
mount -o remount,rw /,编辑 fstab 加nofail,然后reboot。
根治方案就是把所有非关键挂载都加上nofail,并把 timeout 从默认的 90 秒调低到 10 秒左右(在 fstab 里写x-systemd.device-timeout=10)。
6.2 btrfs、overlay、容器里的 swapfile 为什么起不来
btrfs 文件系统上直接创建 swapfile 会失败,报Invalid argument,因为 btrfs 的 CoW 特性和 swap 的固定块映射要求冲突。解决办法是创建时用chattr +C关闭 CoW:
truncate -s 0 /swapfile chattr +C /swapfile fallocate -l 4G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile容器里的情况更复杂。Docker 默认不允许容器内启用 swap,需要宿主机的 cgroup 配置配合,而且大多数编排系统本身就不推荐容器用 swap。在容器里看到swapon: Operation not permitted,基本就是权限和 cgroup 限制,不是配置写错了。虚拟机里装 Linux 时如果选了 LVM 自动分区,安装器一般会自动给一个 swap 逻辑卷,这时候就别再手动加 swapfile 了,容易变成两份 swap 抢优先级。
6.3 swap 莫名满掉:从 smem 到 smaps 的定位过程
有一次我发现 swap 用了 90%,但free -h显示物理内存还剩不少,非常反直觉。定位过程是这样的:
# 1. 看哪些进程占用 swap 最多 smem -t -k -s swap | tail -20 # 没有 smem 的话,用这个脚本遍历 for pid in /proc/[0-9]*; do s=$(awk '/VmSwap/{print $2}' $pid/status 2>/dev/null) [ -n "$s" ] && [ "$s" -gt 0 ] && echo "$s ${pid#/proc/} $(cat $pid/comm 2>/dev/null)" done | sort -rn | head -20结果是几个长期运行的 Java 进程,每个占了几百 MB 的 VmSwap。原因是 JVM 的堆被换出后,GC 需要访问这些页时又得换回来,形成反复换入换出的抖动。这种场景下的处理方式和"内存不够"完全不同:要么调 JVM 参数(-XX:+AlwaysPreTouch让堆在启动时就全部落盘),要么把 swappiness 降到 1,让内核尽量别碰匿名页。
这个案例说明一件事:swap 使用率高不一定是内存不够,也可能是某类进程的内存访问模式被内核误判为"冷页"。用/proc/<pid>/smaps里的 VmSwap 字段可以精确定位到具体是哪几个进程,比看总量的free有用得多。
7. 不同发行版与运行环境的差异处理
同样一条mkswap,在不同发行版和环境下表现可能不一样,这也是很多教程"照着做却失败"的原因。
7.1 Debian 系、Kali 与 Ubuntu 的默认行为
Ubuntu 从 18.04 开始在安装时默认创建/swap.img文件而不是分区,容量跟内存挂钩,小内存机器给到内存大小,大内存机器固定 2G 左右。所以在 Ubuntu 上做扩容,你操作的是文件不是分区。Kali 和 Debian 的默认安装一般走分区方案,用swapon --show能看到/dev/sda5这类设备。做 Kali 学习笔记类的实验时,如果想让快照体积小一点,把 swap 换成一块小的 swapfile 是常见做法,因为虚拟化平台快照会把 swap 分区的内容也一起存下来。
顺带提一句,debootstrap或者自定义安装出来的最小系统往往压根没有 swap,需要手动补。这类系统里 udev 规则可能不完整,partprobe之后设备节点没出现的情况我都遇到过,这时候partx -a /dev/sda是有效的补充手段。
7.2 云主机、虚拟机与嵌入式设备的取舍
| 环境 | 建议方案 | 理由 |
|---|---|---|
| 云主机(SSD) | 2~4G swapfile | 块存储 IOPS 有限,swapfile 调整灵活 |
| 本地虚拟化 | 分区或 LV | 快照管理和容量规划更清晰 |
| 嵌入式(eMMC/SD) | zram 优先 | 避免闪存写入磨损 |
| 容器宿主 | 通常禁用 | 编排系统的调度假设需要 |
| 小内存 VPS(1G 以下) | zram + 小 swapfile | 压缩比高,防 OOM 效果好 |
云上还有个细节:部分云厂商的镜像在/etc/cloud/cloud.cfg里配置了 swap 的自动管理逻辑,你手动改完之后重启会被覆盖回去。遇到"改了 fstab 重启又变回去"的情况,先翻一下 cloud-init 相关配置。
7.3 面试里常被追问的几个点
关于 swap 的面试题其实翻来覆去就那几个,答的时候能把原理串起来就很出彩:
- "swap 满了会怎样?"不是立刻 OOM。swap 满之后匿名页无法换出,回收压力全落到页缓存上,页缓存被砍完就触发 OOM Killer,它会挑
oom_score最高的进程杀。可以调vm.overcommit_memory和 oom_score_adj 来影响这个行为。 - "为什么数据库服务器建议关 swap?"因为换页会让查询延迟出现无法预测的毛刺,而这种抖动在延迟敏感型业务里比直接报错更难排查。
- "swappiness 设成 0 是不是就完全不换页了?"不是。设为 0 只是在同等条件下优先回收文件页,当内存实在回收不出来时,内核仍然会换出匿名页。想彻底不换只能
swapoff。 - "swap 和 tmpfs 有什么区别?"一个往磁盘写,一个往内存写;tmpfs 占用的是内存和 swap 空间(如果 swap 存在,tmpfs 的页也可以被换出),这点很多人答不上来。
我个人在实际操作中的体会是,swap 相关的操作本身都不难,难的是判断"该不该动"和"动完之后怎么验证"。每一次改完 fstab 或者调整 swappiness,我都会重新跑一遍free -h、swapon --show、vmstat 1 10这套组合,观察至少一分钟的实时数据,确认 si/so 和内存水位都在预期范围内再收工。还有个小技巧是把变更前的sysctl -a | grep vm.和cat /proc/swaps输出存一份到/root/下,出问题的时候对照着回滚,比凭记忆猜快得多。