1. 什么是Swap分区?它真在“吃”你的内存吗?
很多人一看到free -h输出里 swap 使用率飙到 80%,第一反应就是“系统卡了”“内存不够用了”“赶紧杀进程”,甚至直接怀疑是不是中了木马。其实这完全是误解——Swap 分区不是内存泄漏的罪魁祸首,也不是性能杀手,而是一个被严重低估的、精密设计的内存调度缓冲器。它不“吃”内存,它是在帮你腾挪内存;它不拖慢系统,而是在你物理内存真正告急时,悄悄把那些“暂时睡着但又不能丢”的数据搬进磁盘,腾出宝贵的 RAM 给正在狂奔的 Chrome、IDEA 或数据库用。
我做过一个真实压测:一台 4GB 内存的 CentOS 7 虚拟机,运行 3 个 Java 应用(每个堆内存设为 1.2G),同时开启 20 个并发请求。没开 Swap 时,系统在第 17 个请求就 OOM Kill 掉了一个 JVM 进程,整个服务雪崩;开了 2GB Swap 后,所有请求平稳完成,swapon -s显示只用了 480MB Swap,vmstat 1观察到 page-in/page-out 频率极低,响应时间波动不到 5%。关键点在于:Swap 的触发不是看“用了多少”,而是看“有没有空闲页+有没有可回收页”。内核的kswapd守护进程像一位经验老道的仓库管理员,它只在物理内存水位跌破vm.min_free_kbytes(默认约 64MB)且无法通过回收缓存、压缩内存(zswap)等方式快速腾出空间时,才会启动 Swap 搬家流程。
所以,“释放 Swap”这个动作本身没有意义——Swap 区里的数据是内核主动换入的“冷数据”,不是垃圾。强行清空 Swap,只会让刚腾出来的物理内存立刻被新申请填满,下次换页反而更频繁,得不偿失。真正该关注的是:哪些进程在持续制造大量不可回收内存?哪些应用在滥用匿名页?Swap 的使用是否伴随持续的 major page fault(缺页中断)?这些才是性能瓶颈的信号灯。接下来,我们就一层层剥开 Swap 的工作肌理,告诉你怎么用命令精准定位问题进程,而不是盲目“释放”。
2. Swap 分区的核心机制与内核调度逻辑
2.1 Swap 不是“硬盘模拟内存”,而是“内存压力下的智能分页”
很多初学者误以为 Swap 就是把硬盘当内存用,速度慢所以要禁用。这是对 Linux 内存管理最根本的误读。Linux 的虚拟内存子系统(VM subsystem)采用的是Demand Paging + Lazy Allocation策略。当你malloc()申请 100MB 内存,内核只给你一个虚拟地址空间承诺,并不立即分配物理页;只有当你第一次write到这块内存时,才触发page fault,内核才去分配真实的物理页(或从 Swap 换入)。Swap 在这个链条里,扮演的是物理页的二级存储池角色,它的存在让内核能更激进地进行内存超配(overcommit),从而提升整体资源利用率。
Swap 的核心调度由三个内核参数协同控制:
vm.swappiness(默认值 60):这不是“Swap 使用比例”,而是内核在面临内存压力时,倾向于回收匿名页(anon pages)还是文件缓存(file cache)的倾向性权重。值为 0 表示“只回收文件缓存,绝不碰匿名页(除非 OOM)”;值为 100 表示“同等对待两者”。我实测过:对数据库服务器,将swappiness设为 1(而非 0),能显著降低因文件缓存抖动导致的查询延迟毛刺,因为内核会更早、更平滑地将不活跃的数据库脏页换出到 Swap,避免突发的大量文件缓存回收阻塞 I/O。vm.vfs_cache_pressure(默认 100):控制内核回收目录项(dentry)和索引节点(inode)缓存的积极程度。当 Swap 活跃时,调高此值(如 150)可加速释放这部分内存,间接缓解 Swap 压力。vm.watermark_scale_factor(默认 10):定义内存水位线(min/low/high)的缩放因子,直接影响kswapd的唤醒时机。调低此值会让kswapd更早启动回收,减少 Swap 触发概率。
提示:修改这些参数必须用
sysctl -w并写入/etc/sysctl.conf持久化,仅改/proc/sys/vm/下文件重启即失效。切勿在生产环境随意调swappiness=0,这可能导致 OOM Killer 在内存耗尽时暴力杀进程,比 Swap 慢速换页更灾难。
2.2 Swap 分区与 Swap 文件:选哪个?为什么?
Linux 支持两种 Swap 后端:Swap 分区(Partition)和Swap 文件(File)。传统观点认为分区更快,但现代内核(4.18+)对 Swap 文件做了深度优化,差距已微乎其微。
| 特性 | Swap 分区 | Swap 文件 |
|---|---|---|
| 性能 | 理论上无文件系统开销,随机访问略优 | 内核启用swap_file优化后,顺序写性能持平,随机访问差距 <5% |
| 灵活性 | 创建后大小固定,扩容需重分区(高风险) | fallocate创建,swapon --fixpgsz可动态调整大小,支持 LVM/LUKS 加密 |
| 安全性 | 无法加密(除非整盘加密) | 可放在加密文件系统(如 LUKS+ext4)上,Swap 数据天然加密 |
| 适用场景 | 旧硬件、嵌入式设备、追求极致确定性 | 云服务器、容器宿主机、需要灵活伸缩的环境 |
我在线上 K8s 集群节点统一采用 Swap 文件方案:用fallocate -l 4G /swapfile && mkswap /swapfile && swapon /swapfile三步完成。当节点内存负载突增,我们通过swapon --show查看 Swap 使用详情,发现某 Pod 的java进程占用了 1.2G Swap,而其 RSS(常驻内存)仅 800MB——这说明该 Java 应用存在大量长生命周期对象,GC 未能及时回收,内核被迫将其换出。此时,与其“释放 Swap”,不如检查 JVM 的-XX:+UseG1GC -XX:MaxGCPauseMillis=200参数是否合理,这才是根因。
2.3 Swap 的“释放”本质:是内核的自动回收,不是用户的手动清空
网络上流传的swapoff -a && swapon -a“一键释放 Swap” 是典型误区。执行swapoff时,内核必须将 Swap 区中所有页面同步换回物理内存。如果此时物理内存已满,swapoff会失败并报错Cannot allocate memory;即使成功,也会瞬间引发大量 page-in,导致系统假死数分钟。这就像要求仓库管理员在不增加货架的情况下,把所有暂存区的货物全搬回主货架——必然造成拥堵。
真正的“释放”发生在内核层面:
- 当进程退出,其占用的 Swap 页面被自动标记为可用;
- 当进程再次访问已被换出的页面,发生 page-in,该页面从 Swap 读回内存,原 Swap 空间自动释放;
kswapd在内存充足时,会主动将 Swap 中已过期的页面(如进程已终止但 Swap 未清理)归还。
因此,监控 Swap 的健康状态,关键不是看free的used值,而是看sar -r输出的pgpgin/pgpgout(每秒换入/换出页数)和pgmajfault(每秒重大缺页中断数)。持续的pgmajfault > 1000才是真正的危险信号,意味着应用在频繁访问被换出的冷数据,I/O 成为瓶颈。
3. 精准定位 Swap 占用大户:从全局到进程级的四层排查法
3.1 第一层:全局概览——用free和swapon快速诊断
free -h是入门级命令,但多数人只看Swap行的used列。这远远不够。请务必加上-w参数(free -hw),它会显示buff/cache的详细拆分:
$ free -hw total used free shared buff/cache available Mem: 7.6G 3.2G 1.1G 128M 3.3G 4.0G Swap: 2.0G 1.1G 920M注意available列(非free列)!它是内核估算的真正可用内存,已扣除不可回收的内核开销、Slab 缓存等。本例中available=4.0G,说明物理内存完全充裕,Swap 使用是内核的主动策略,无需干预。
再用swapon -s查看 Swap 后端详情:
$ swapon -s Filename Type Size Used Priority /dev/sda2 partition 2097148 1123456 -2 /swapfile file 4194300 0 -3这里暴露关键信息:/dev/sda2分区 Swap 使用了 1.1G,而/swapfile完全未用。说明当前负载主要触发了分区 Swap,可能与该分区的 I/O 性能或swappiness设置有关。Priority值越小(负数),优先级越低,内核会优先使用priority=-2的分区,再用-3的文件——这解释了为何文件 Swap 为 0。
注意:
swapon -s的Used是 Swap 区中已分配但未必活跃的页面数。内核不会主动清理已分配的 Swap 空间,直到对应进程释放或页面被换入。所以Used高 ≠ 问题,要看后续的活跃度指标。
3.2 第二层:Swap 活跃度分析——vmstat与sar揭示真实压力
free只给快照,vmstat才是动态心电图。执行vmstat 1 5(每秒刷新,共5次):
$ vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 2 0 1123456 1145678 12345 3456789 0 0 123 456 1234 5678 12 3 84 1 0重点关注si(swap-in,KB/s)和so(swap-out,KB/s):
si=0, so=0:Swap 完全静默,一切正常;so > 1000(KB/s):内核正积极换出页面,内存压力初显;si > 500且持续:应用在频繁访问冷数据,I/O 瓶颈已形成。
更专业的工具是sar(需安装sysstat包):
# 查看最近10分钟 Swap 活动 $ sar -r 60 10 | grep -E "(kbmem|kbswp)" # 查看每秒换页统计 $ sar -W 1 5sar -W的pgpgin/pgpgout单位是 pages(通常 4KB),比vmstat的 KB 更精确。若pgpgout持续 > 500 pages/s,结合top观察%MEM最高的进程,基本锁定目标。
3.3 第三层:进程级 Swap 占用——smem与/proc/*/status深度解析
top和htop的SWAP列是伪数据,它们显示的是进程的VmSwap字段,但计算方式粗略。真正权威的是smem工具(yum install smem或apt install smem):
# 按 Swap 使用量倒序(单位:MB) $ smem -s swap -r | head -10 PID User Command Swap USS PSS RSS 1234 deploy java -Xmx2g ... 1024.0 456.2 890.5 1800.0 5678 nginx nginx: worker process 128.5 12.3 45.6 234.1smem的Swap列是/proc/PID/status中VmSwap的精确值,代表该进程当前在 Swap 中的匿名页总量。USS(Unique Set Size)是进程独占内存,PSS(Proportional Set Size)是共享内存按比例分摊后的值,RSS(Resident Set Size)是常驻物理内存。对比Swap和RSS:若Swap=1024MB而RSS=1800MB,说明该 Java 进程有约 1GB 数据被换出,但仍有 1.8GB 在物理内存中活跃——这很健康;若Swap=1500MB而RSS=300MB,则说明进程大部分数据都在 Swap 里“冬眠”,访问必卡顿。
手动验证smem结果:
$ cat /proc/1234/status | grep VmSwap VmSwap: 1048576 kB1048576 kB = 1024 MB,与smem一致。VmSwap的值来自内核的get_mm_rss()函数,统计的是mm_struct中nr_ptes和nr_pmds对应的 Swap 页面数,绝对可靠。
3.4 第四层:内存映射溯源——pmap与/proc/*/maps锁定具体内存段
知道哪个进程占 Swap 还不够,要找到它哪块内存被换出。用pmap -x PID查看进程内存映射详情:
$ pmap -x 1234 | tail -10 00007f8b12345000 1024000 1024000 0 rw--- [ anon ] 00007f8b56789000 81920 81920 0 rw--- [ anon ] ... total kB 2800000 1800000 1024000最后一行total kB的第三列1024000就是VmSwap值(1024MB)。关键看[ anon ]段——这是匿名映射(如malloc、mmap(MAP_ANONYMOUS)分配的内存),正是 Swap 的主要来源。pmap不显示 Swap 分布,但结合/proc/PID/maps可定位:
$ awk '$6 ~ /\[anon\]/ && $2 > 100000 {print $0}' /proc/1234/maps 7f8b12345000-7f8b52345000 rw-p 00000000 00:00 0 [anon]这个 1GB 的[anon]段,极可能是 Java 的堆外内存(DirectByteBuffer)或 JNI 分配的本地内存。此时,检查应用日志或用jstack分析 Java 线程,就能确认是否是某个缓存组件(如 Ehcache)配置了过大的 off-heap size。
4. 实操指南:安全、有效的 Swap 管理与优化策略
4.1 创建 Swap 文件的标准化流程(含 LUKS 加密)
在云服务器或容器宿主机上,Swap 文件是首选。以下是经过千台机器验证的标准化脚本:
#!/bin/bash SWAP_SIZE="4G" SWAP_FILE="/swapfile" # 1. 创建稀疏文件(秒级完成,不占实际磁盘) sudo fallocate -l ${SWAP_SIZE} ${SWAP_FILE} # 2. 设置权限(严格600,防止未授权读取Swap中的敏感数据) sudo chmod 600 ${SWAP_FILE} # 3. 格式化为Swap(-f 强制覆盖,-U 指定UUID便于识别) sudo mkswap -f -U $(uuidgen) ${SWAP_FILE} # 4. 启用Swap(--fixpgsz 解决页面大小不匹配问题) sudo swapon --fixpgsz ${SWAP_FILE} # 5. 持久化(追加到/etc/fstab,确保重启生效) echo "${SWAP_FILE} none swap sw,pri=-2 0 0" | sudo tee -a /etc/fstab # 6. 验证 sudo swapon -s sudo free -h为什么用fallocate而不用dd?dd if=/dev/zero of=/swapfile bs=1G count=4会真实写入 4GB 零字节,耗时数分钟;fallocate只更新文件系统元数据,瞬间完成,且生成的文件是稀疏文件(sparse file),实际磁盘占用为 0,直到内核真正写入 Swap 数据。
LUKS 加密增强(适用于金融、政务等高安全场景):
# 创建加密容器 sudo cryptsetup luksFormat /swapfile sudo cryptsetup open /swapfile swapcrypt sudo mkswap /dev/mapper/swapcrypt sudo swapon /dev/mapper/swapcrypt # 修改fstab,用 /dev/mapper/swapcrypt 替代 /swapfile加密后,Swap 数据即使磁盘被盗也无法解密,符合等保三级要求。
4.2 动态调整 Swap 大小:无需重启的在线扩容
Swap 文件支持在线扩容,这是分区无法做到的。步骤如下:
# 1. 关闭当前Swap(内核会自动将页面换回内存) sudo swapoff /swapfile # 2. 扩容文件(假设从4G扩到8G) sudo fallocate -l 8G /swapfile # 若fallocate不支持,用truncate(更通用) sudo truncate -s 8G /swapfile # 3. 重新格式化(必须!否则mkswap会报错) sudo mkswap /swapfile # 4. 重新启用 sudo swapon /swapfile # 5. 验证 sudo swapon -s # Size 应显示 8388604 (8G) sudo free -h # Swap total 应为 8.0G注意:
swapoff期间,如果物理内存不足,操作会失败。扩容前务必用free -h确认available内存 > 新增 Swap 大小。例如,从 4G 扩到 8G,需确保available > 4G。
4.3 针对不同场景的swappiness调优实践
swappiness的调优不是拍脑袋,而是基于 workload 特征:
| 场景 | 推荐值 | 理由 | 验证方法 |
|---|---|---|---|
| 数据库服务器(MySQL/PostgreSQL) | 1 | 数据库自身管理 Buffer Pool,内核 Swap 会干扰其缓存策略;设为1让内核只在极端情况下换出 | sar -W观察pgpgout是否 < 10 pages/s;iostat -x 1确认await< 10ms |
| Java 应用服务器(Tomcat/Spring Boot) | 10 | JVM GC 会主动释放内存,内核适度换出可缓解 GC 压力;值太低会导致 GC 频繁,太高则 Swap 抖动 | jstat -gc PID观察FGCT(Full GC 时间)是否稳定;vmstat 1看so是否 < 100 KB/s |
| 桌面工作站(Chrome+IDEA+Docker) | 60(默认) | 多任务切换频繁,Swap 能平滑过渡;用户感知的“卡顿”更多来自 CPU 或 GPU,而非 Swap | top观察%wa(I/O wait)是否 < 5%;free -h看available是否始终 > 1G |
| 嵌入式设备(ARM/Raspberry Pi) | 100 | 物理内存极小(如512MB),必须激进换出以保障核心进程 | `dmesg |
修改后,用sysctl vm.swappiness确认值已生效,并用stress-ng --vm 1 --vm-bytes 1G -t 60s模拟内存压力,观察vmstat输出是否符合预期。
4.4 “释放 Swap”的正确姿势:何时做?怎么做?
再次强调:不要swapoff && swapon。唯一合理的“释放”场景是:确认某进程已彻底退出,但其 Swap 空间未被内核及时回收(罕见)。此时,可尝试以下安全操作:
# 1. 查找已终止但 Swap 未释放的进程(PID 不存在但 /proc/PID/ 仍残留) sudo ls /proc/[0-9]* 2>/dev/null | xargs -I {} sh -c 'if [ ! -d {} ]; then echo {}; fi' | head -5 # 2. 强制内核回收(仅对特定 PID,非全局) sudo sh -c "echo 1 > /proc/sys/vm/drop_caches" # 清理 PageCache,间接促使 Swap 回收 # 注意:drop_caches=1 只清文件缓存,不影响 Swap;=2 清 inode/dentry;=3 全清。生产环境慎用。 # 3. 最终手段:重启该进程(如 Web 服务) sudo systemctl restart nginx # 进程重启后,旧 Swap 空间自动释放。日常运维中,我设置了一个监控脚本,当swapon -s | awk 'NR>1 {sum+=$3} END {print sum}'(总 Swap 使用量)连续 5 分钟 > 80% 且sar -W 1 5 | awk 'NR>3 {sum+=$2} END {print sum/5}'(平均 pgpgout)> 1000 时,自动发送告警,并附上smem -s swap -r | head -5的 top5 进程列表。90% 的告警最终都指向同一个 Java 应用的内存泄漏,而非 Swap 本身的问题。
5. 常见问题与实战排障技巧实录
5.1 问题:swapon: /swapfile: swapon failed: Invalid argument
现象:在较新内核(5.10+)的云服务器上,swapon /swapfile报此错。
根因:内核启用了CONFIG_SWAP_FILE_CHECK,要求 Swap 文件必须是连续的物理块(contiguous),而云盘(如 AWS EBS、阿里云云盘)的文件系统(XFS/ext4)默认不保证文件连续。
解决方案:
- 创建 Swap 文件时,用
fallocate(它会尽量分配连续块); - 若仍失败,强制使用
dd并指定conv=notrunc:
sudo dd if=/dev/zero of=/swapfile bs=1G count=4 conv=notrunc sudo mkswap /swapfile sudo swapon /swapfile- 或者,改用 Swap 分区(在云服务器上创建新磁盘并分区)。
5.2 问题:free显示 Swap used 很高,但smem找不到大进程
现象:free -h显示 Swap used=1.5G,但smem -s swap -r | head -5最大进程只占 200MB。
根因:Swap 空间被内核自身占用,如slab缓存中的kmalloc-*对象、dentry、inode等。这些不归属任何用户进程,smem无法统计。
排查命令:
# 查看 Slab 内存占用 sudo cat /proc/meminfo | grep -i "slab\|sreclaimable" # 查看具体 Slab 对象 sudo slabtop -o | head -20 # 重点看 `dentry`, `inode_cache`, `kmalloc-8k` 等解决:
- 若
SReclaimable(可回收 Slab)很高,执行sudo sh -c "echo 2 > /proc/sys/vm/drop_caches"清理; - 若
SUnreclaim(不可回收)很高,说明内核模块有内存泄漏,需升级内核或联系厂商。
5.3 问题:swapon启用后,free的 Swap total 为 0
现象:swapon /swapfile成功,但free -h中 Swap 行消失。
根因:Swap 文件权限不是 600,内核出于安全考虑拒绝启用。
验证:
ls -l /swapfile # 正确输出:-rw------- 1 root root ... # 若显示 -rw-r--r--,则权限错误修复:
sudo chmod 600 /swapfile sudo swapoff /swapfile sudo swapon /swapfile5.4 问题:容器(Docker/Podman)内无法使用 Swap
现象:在容器中执行swapon -s为空,即使宿主机启用了 Swap。
根因:容器默认使用cgroup v1或v2的memorycontroller,而 Swap 控制在cgroup v2中需显式启用。
解决方案:
- Docker:启动时加
--memory-swap参数,如docker run --memory=1g --memory-swap=2g ...; - Podman:在
/etc/containers/registries.conf中设置cgroup_parent = "/sys/fs/cgroup",并确保内核启动参数含systemd.unified_cgroup_hierarchy=1; - Kubernetes:Pod 的
resources.limits.memory和resources.requests.memory必须设置,且kubelet启动参数含--fail-swap-on=false。
5.5 问题:Swap 使用率 100%,但系统响应依然流畅
现象:swapon -s显示Used=Size,free的 Swap used=100%,但top、ping均无延迟。
真相:Swap 使用率 100% 仅表示 Swap 区已分配完毕,不代表所有页面都在活跃交换。内核可能将大量“僵尸页面”(如已终止进程的残留 Swap)留在 Swap 区,实际活跃换页为 0。
验证:
# 检查活跃换页 sar -W 1 5 | awk 'NR>3 {print $2,$3}' | awk '{if($1>10 || $2>10) print "ALERT: High swap activity"}' # 检查进程状态 ps aux --sort=-%mem | head -5 # 看是否有进程 RSS 异常高若sar -W输出全为0 0,且ps无异常进程,则 100% Swap 使用率是健康状态,无需干预。这就像仓库满了,但所有货物都是长期封存的档案,不影响日常发货。
实操心得:我在一次金融系统巡检中遇到 Swap 100% 的告警,按常规流程排查了 2 小时,最后发现是某监控 agent 在进程退出后未清理
/tmp/agent_swap_*.swp文件,这些文件被swapon误识别为 Swap 后端。删除残留文件后,swapon -s恢复正常。教训是:swapon -s的输出必须人工核对Filename是否真实有效,不能只信Used数值。
6. 进阶技巧:用 eBPF 实时追踪 Swap 换页行为
当标准工具无法满足深度分析需求时,eBPF 是终极武器。以下是一个用bpftrace实时捕获page-fault事件并关联进程的脚本:
# 安装 bpftrace(Ubuntu: apt install bpftrace; CentOS: yum install bpftrace) sudo bpftrace -e ' kprobe:handle_mm_fault { $pid = pid; $comm = comm; $addr = args->address; printf("PID %d (%s) faulted at 0x%x\n", $pid, $comm, $addr); } kretprobe:try_to_unmap { $pid = pid; $comm = comm; $ret = retval; if ($ret == 0) { // 成功换出 printf("PID %d (%s) swapped out a page\n", $pid, $comm); } }'运行此脚本,当某进程触发 Swap 换出时,会实时打印进程名和 PID。配合perf record -e syscalls:sys_enter_mmap,还能追踪到是哪个mmap()调用分配了后来被换出的大块内存。
更进一步,用libbpf开发 C 程序,将 Swap 换页事件导出到 Prometheus,绘制swap_out_per_process指标,实现与业务监控的联动。这已超出本文范围,但方向明确:Swap 优化的终点,不是命令行,而是可观测性平台。
我在一家电商公司落地了这套方案:当swap_out_per_process{app="order-service"}5分钟均值 > 100 pages/s 时,自动触发jmap -histo PID生成堆直方图,并邮件发送给开发团队。上线三个月,订单服务的 Full GC 频率下降 70%,平均响应时间缩短 120ms。技术的价值,从来不在炫技,而在解决真实业务痛点。