Swap 满了但物理内存有剩余
01 前置理论与核心机制¶
1. Swap 满了,为什么内存还有剩余?¶
先看这台测试虚拟机的结果:
total used free shared buff/cache available
Mem: 1.9Gi 352Mi 1.5Gi 1.0Mi 86Mi 1.4Gi
Swap: 1.0Gi 1.0Gi 0B
Swap 已经占满,但物理内存还有 1.4 GiB 可用。
原因是:之前内存紧张时,有些数据被换到了 Swap;后来内存空出来了,这些数据却没有马上搬回来。
这里先认识两个值:
-
available:系统估算还能提供给程序使用的内存。排查时主要看这个值。
-
Swap used:现在有多少 Swap 空间被占用。它记录的是占用量,不是读写速度。
2. Linux 为什么不把 Swap 里的数据立即搬回来?¶
假设程序 A 占了 1GB 内存,程序 B 又突然申请大量内存。物理内存不够用,Linux 就会把程序 A 暂时不用的部分数据放进 Swap,给程序 B 腾位置。
后来程序 B 退出,物理内存空出来了。但程序 A 还活着,也一直没用到刚才那部分数据,Linux 就继续把它们留在 Swap 中。
等程序 A 再次访问这些数据时,Linux 才把需要的数据读回内存。
所以,Swap 满了,物理内存还有很多剩余,这两个现象并不矛盾。
3. Swap 满了,怎么知道有没有影响业务?¶
看两件事:
-
现在还在不在频繁读写 Swap。 用 vmstat 查看 si、so。
-
业务有没有变慢。 看接口响应、数据库查询耗时等实际指标。
如果 si、so 基本是 0,说明查看的这几秒里几乎没有从 Swap 读入或写出数据。这是当前没有频繁换页的表现,不代表 Swap 中的数据以后读取时没有开销。
02 故障排查与验证闭环 SOP¶
步骤一:查看物理内存和 Swap¶
先看 available 还有多少,再看 Swap 已用了多少。swapon --show 还能告诉你 Swap 用的是文件还是分区。
步骤二:检查当前有没有频繁换页¶
重点看:
-
si:每秒从 Swap 读回物理内存的数据量。
-
so:每秒从物理内存写入 Swap 的数据量。
第一行通常是系统启动以来的平均值,重点看后面 4 行实时采样。
这次测试里,后几次 si、so 基本都是 0,说明系统当时没有持续频繁地读写 Swap。
步骤三:查看哪些进程占用了 Swap¶
第 07 章提供 check_swap.sh,直接执行:
这台测试机的输出:PID PROCESS SWAP(MB)
2446 python3 974.9
1392 dockerd 9.6
969 containerd 5.4
946 networkd-dispat 4.7
1021 unattended-upgr 4.3
步骤四:查看这个进程的内存详情¶
实际输出:-
VmRSS:这个进程现在有多少数据留在物理内存里,约 31MB。
-
VmSwap:这个进程有多少数据放在 Swap 里,约 975MB。
也就是说,这个进程的大部分数据已经被换出,但整台机器的物理内存又空出了 1.4GB。 要解释的现象就在这里。注意一个细节:VmSwap 是进程级统计,统计范围和系统总 Swap 不完全相同,所以把每个进程的 VmSwap 加起来,不一定刚好等于 free -h 的 Swap 使用量。
步骤五:根据排查结果处理¶
情况一:Swap 已满,但业务运行正常。available 还有较大余量,vmstat 里 si、so 基本为 0,接口和数据库响应也正常。这说明数据虽然留在 Swap 中,但当前没有持续换页造成的性能问题。保持现状,继续监控即可。情况二:Swap 占用持续增长,业务开始变慢。vmstat 连续多次出现较大的 si、so,说明系统正在反复换入、换出数据。结合 check_swap.sh 查找主要进程,再检查该进程的内存使用、业务负载和服务器容量。找到内存压力来源后,按情况处理应用、调整配置或扩容。不要为了让 Swap 数字归零就直接执行 swapoff -a。 这条命令会停用 Swap,可能需要把大量数据搬回物理内存。内存装不下时,操作可能失败,严重时会触发 OOM,杀掉业务进程。
03 生产故障案例与处理分析(模拟场景)¶
案例一:MySQL 服务器 Swap 告警,但数据库没有变慢¶
故障现象一台 MySQL 服务器告警,Swap 使用率达到 98%。值班人员查看监控,发现数据库查询耗时正常,物理内存还有 8GB 可用。排查过程先查看内存:
确认 Swap 已用得差不多,但物理内存还有余量。再观察 10 秒: 后续采样中 si、so 基本为 0,说明当时没有频繁交换内存。接着查占用进程: MySQL 进程占用了较多 Swap。再查看历史监控,发现凌晨有一次批量任务,内存使用明显上升;批量任务结束后,物理内存降下来了,但 MySQL 的部分不活跃内存页仍留在 Swap 中。原因和处理这次告警是之前内存紧张留下的 Swap 占用,并不是数据库此刻还在频繁换页。数据库查询正常,所以没有执行 swapoff -a,而是继续观察数据库延迟和换页指标,并检查凌晨批量任务的内存峰值。最终结论:这台服务器的 Swap 告警可以先观察,真正需要优化的是凌晨批量任务带来的内存峰值。案例二:Java 服务频繁换页,接口从几十毫秒变成几秒¶
故障现象一台 Java 应用服务器,接口原本几十毫秒就能返回,后来响应时间上升到数秒。监控同时显示可用物理内存持续下降,Swap 使用量不断增加。排查过程
这次和案例一不同:available 很低,而且后面多次采样的 si、so 都明显不为 0,说明系统正在频繁交换内存。继续定位进程: 发现 Java 进程占用大量内存,再查看它的启动参数和 JVM 状态:PID=1234 # 替换为实际 Java 进程 PID
ps -p "$PID" -o pid,etime,rss,vsz,args
jcmd "$PID" VM.flags
jcmd "$PID" GC.heap_info
04 生产排障速查表(Swap 与内存压力)¶
排查顺序:先看内存和换页,再找进程,最后结合业务情况决定怎么处理。
05 面试应答参考话术¶
问题:Linux Swap 已经满了,但物理内存还有很多剩余,是什么原因?可以这样回答: Linux 之前内存紧张时,会把一些不活跃的内存页换到 Swap。后来其他程序把物理内存释放了,但原来换出去的数据并不会马上全部搬回来,所以会出现 Swap 满了、物理内存还有很多剩余的情况。
排查时,我会先用 free -h 看 available,再用 vmstat 1 5 查看 si、so,判断系统现在是不是还在频繁换页。然后用脚本找出占用 Swap 的进程。如果业务正常、没有持续换页,就先观察;如果接口变慢、换页很频繁,就继续查具体的内存压力来源。
06 面试高频追问与应答预案¶
Q1:Swap 满了,是不是物理内存也满了?¶
不是。Swap 里的数据可能是以前内存紧张时换进去的。后来物理内存释放了,Swap 仍然保持占用。
Q2:free 和 available 有什么区别?¶
free 是完全没有被使用的内存;available 还把一部分能回收的缓存算进去了,更适合判断现在还能给程序提供多少内存。
Q3:si 和 so 都是 0,说明什么?¶
说明这段采样时间里基本没有数据在物理内存和 Swap 之间搬来搬去。如果以后程序要用到 Swap 中的数据,还是需要重新读取,会带来一定延迟。
Q4:为什么进程的 VmSwap 加起来,和 free -h 对不上?¶
因为两个数据的统计范围不完全相同。free -h 看整台机器,VmSwap 看单个进程,而且部分共享内存的交换占用不会计入 VmSwap。
Q5:把 vm.swappiness 设置为 0,是不是就不用 Swap 了?¶
不是。它影响 Linux 使用 Swap 的倾向,但并没有关闭 Swap。是否使用 Swap 还和内存压力、内核行为有关。
Q6:能不能用 swapoff -a 清理 Swap?¶
可以停用 Swap,但要先确认有足够的物理内存。停用时可能需要把大量数据搬回内存;内存不够时可能失败或触发 OOM。生产环境先查原因,再决定是否操作。
Q7:Swap 占用高和频繁换页,有什么区别?¶
Swap 占用高,是指里面放了很多数据;频繁换页,是指数据正在不断地从内存写到 Swap,或者从 Swap 读回内存。前者看占用量,后者看 si、so。
07 Swap 占用进程自动化诊断脚本(check_swap.sh)¶
这个脚本会列出正在使用 Swap 的进程,显示 PID、名称和占用量,并按占用量从大到小排序。它只负责查看信息,不修改任何系统配置。
#!/usr/bin/env bash
# check_swap.sh:查看各进程的 Swap 占用排行
set -u
tmp=$(mktemp) || exit 1
trap 'rm -f "$tmp"' EXIT
for dir in /proc/[0-9]*; do
pid=${dir##*/}
[[ -r "$dir/status" ]] || continue
kb=$(awk '/^VmSwap:/ {print $2; exit}' "$dir/status" 2>/dev/null) || continue
[[ "$kb" =~ ^[0-9]+$ ]] || continue
(( kb > 0 )) || continue
name=$(cat "$dir/comm" 2>/dev/null) || name='unknown'
mb=$(awk -v x="$kb" 'BEGIN {printf "%.1f", x/1024}')
printf '%s\t%s\t%s\n' "$pid" "$name" "$mb" >> "$tmp"
done
printf '%-10s %-24s %12s\n' 'PID' 'PROCESS' 'SWAP(MB)'
sort -t $'\t' -k3,3nr "$tmp" | head -n 20 | awk -F '\t' '{printf "%-10s %-24.24s %12s\n", $1, $2, $3}'
使用方法¶
保存为 check_swap.sh:
输出示例: 想查看某个进程的完整启动命令,可以执行: