☰
Linux下swap占用高但物理内存充足?原因与排查调优
2026/10/20 11:50:03 网站建设 项目流程

昨天半夜接到告警,登录服务器一看:free -h里 swap 已经吃掉快满格,可物理内存还剩下二十几个 G 的空闲。这种“swap 占用过高、物理内存富余”的局面,做过 Linux 运维或开发的人基本都撞见过,尤其是跑着 Java 服务、数据库、容器平台的机器。第一次遇到的人很容易慌,第一反应是“内存是不是泄漏了”,还有人会直接敲swapoff -a想把 swap 赶紧清空,结果反而把系统搞崩。

先说结论:看到 swap 占用高但物理内存还有空余,大多数情况下不是内存真的不够,而是 Linux 默认的内存回收策略“积极”地把一部分不活跃内存页换到了 swap。真正要排查的不是“swap 又高了”这个表象,而是三个问题:为什么内核要换页?这些 swap 是谁在消费?以及怎么让系统更合理地使用物理内存?下面按我实际排查的顺序把整个思路和操作都写出来。

1. 先别慌:用这几条命令把现场看清楚

1.1 重新读懂 free 输出,别被“空闲”骗了

只要涉及内存问题,第一个命令肯定是free -h。但绝大多数人只盯着free这一列,看到剩余内存还很多,就觉得物理内存充足,然后去怀疑“swap 为什么被占”,方向一开始就偏了。

典型的输出长这样:

total used free shared buff/cache available Mem: 31G 18G 13G 1.2G 9.5G 11G Swap: 19G 10G 8.6G

这里的free 13G是“完全没被程序使用的内存”,而buff/cache 9.5G是内核拿空闲内存做文件缓存、目录缓存等用途。Linux 的设计思路是:空闲内存闲着也是闲着,不如拿来做 cache 加速读写。所以真正反映“还能分出去多少内存”的是available这一列,它把可回收的 cache 也算进去了。

如果available比free高很多,说明系统其实一点都不缺内存,swap 被占用只是内核主动换出。反之,如果available只剩几个 G,而 swap 使用率还很高,那才是真正的内存压力,需要重点查进程。

顺带看下/proc/meminfo,这里信息更细:

cat /proc/meminfo | grep -E '^(MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree|SwapCached|Dirty|Active\(anon\)|Inactive\(anon\))'

SwapCached是已经被换出、但内容还在页缓存里的部分,可以理解为“已经换出但还没真正写回磁盘的页面”。如果这个值大,说明系统近期刚做过换出动作。

1.2 用 vmstat 区分“占着不动”和“持续换进换出”

free只能看到静态快照,还要用vmstat 1看动态趋势:

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 10240 2700 120 950 0 0 5 2 100 150 3 0 96 0 0

重点关注si和so两列:

  • si是每秒从 swap 读入内存的量(swap in),单位 KB/s。
  • so是每秒从内存写入 swap 的量(swap out)。

如果si、so长时间都是 0,那 swap 只是“占着”这些容量,并没有持续的换页动作。这种情况下 swap 里的页面就像进了冷库,除非程序再次访问,否则不会对系统产生额外 IO 开销。

如果si、so持续有数值,哪怕只有几十 KB,说明内核正在频繁搬运内存页。这时候不要觉得“内存还有空余就没事”,因为换页一旦持续发生,CPU 和磁盘 IO 都会被拖累,应用响应会明显变慢。

现象si/so处置方向
swap 占用高,si/so 几乎为 00/0优化 swappiness,低峰期手动释放一次
swap 占用高,si/so 持续增长大量换页优先排查内存压力、进程占用,必要时扩容
swap 占用不高,但 si/so 高有换页动作查是否有瞬时内存尖峰,检查大页、脏页回收

1.3 再看一眼负载、IO 等待和大进程

top -o %MEM按内存排序,或者htop按 MEM% 排序,能快速看到哪些进程吃内存。不过注意:top里显示的 RES 是物理内存占用,不直接显示 swap 占用,除非按O选择排序字段,或者在htop里打开Swap列。

同时留意uptime的负载和vmstat的wa(IO wait)列。如果 swap 持续换页,IO 等待会升高,这时候系统的卡顿感就出来了。

2. 为什么物理内存还富余,swap 却被塞满了

2.1 swap 不是“内存不够才用”的备胎

很多人对 swap 的理解是:内存满了才开始用。真实的 Linux 内存回收机制完全不是这样。内核有一个名为 kswapd 的内核线程,会周期性地检查内存水位线,一旦发现内存低于某个阈值,就会启动回收。回收对象有两种:

  • 文件页(file-backed pages):也就是读文件产生的页缓存,这种页面可以直接丢弃或写回磁盘后释放。
  • 匿名页(anonymous pages):进程堆、栈这类没有文件背景的内存页,必须写到 swap 才能腾出来。

关键是:kswapd 并不只会在内存完全耗尽时才启动回收。为了让系统始终留有足够的空闲页,它会在水位线附近提前干活。它按优先级从低到高选择回收对象:先回收文件页缓存,如果回收得不够,或者系统认为匿名页更适合换出,就开始把部分匿名页写进 swap。

所以,物理内存明明还有空余,swap 里却堆着不少页面,完全可能。这不是内核“偷懒”,而是它认为把不活跃的匿名页换出去,比保留大量文件缓存更划算。也可以把它理解成“整理房间”:沙发还没坐满,但你已经把很久不用的冬衣收进储物柜了。

2.2 核心参数 swappiness:内核的“换出偏好”

控制上述偏好的核心参数叫swappiness。多数发行版默认值是 60,范围是 0 到 100,部分新内核支持到 200,但日常服务器默认 60 就够用了。

这个值怎么理解?简单说,它表示内核在回收内存时,有多大倾向去回收匿名页(也就是要用到 swap)。值越高,内核越愿意把匿名页换到 swap,从而保留更多文件缓存;值越低,内核越不愿意用 swap,优先回收文件页缓存。

对跑业务的服务,swap 操作比内存访问慢几个数量级。如果物理内存还算宽裕,把 swappiness 调低,让内核尽量先把 cache 让出来,而不是把进程的堆栈换进 swap,通常更划算。

可用下面命令临时查看和修改:

cat /proc/sys/vm/swappiness sysctl vm.swappiness=10

sysctl命令行直接修改只对当前生效,重启后恢复默认,后面我会讲永久配置。

2.3 水位线机制:内核为了让系统“不卡”而提前换出

另一个容易忽略的原因是内存水位线。内核为每个内存节点维护了low、high、min等水位线。当空闲内存低于low水位线时,kswapd 开始后台回收;当低于min时,系统会进入直接内存回收,这种回收会同步阻塞分配内存的进程,导致非常明显的卡顿。

为了避免出现这种“被动卡死”的局面,内核宁可提前做一点 swap 换出,也不愿等到真正内存枯竭。所以即使free显示还有十几个 G,只要空闲页跌破某个水位线,后台回收线程就会不声不响地把一部分内存页挪到 swap。尤其在某些分配内存特别快的场景下,水位线设置偏大,swap 的“提前用量”也会跟着变大。

2.4 cgroup、容器和 NUMA:三个容易被忽略的“隐形推手”

如果这台机器跑着容器,或者启用了 NUMA,事情会更复杂。

容器场景下,即使宿主机内存富余,如果容器平台给某个容器设置了 swap 限制,容器内进程会主动使用 swap。某些容器运行时或编排平台会为容器配置--memory-swap、memory.swap.max这类参数,限制容器可使用的内存和 swap 总量。当容器内进程超过物理内存限制后,哪怕宿主机还有很多空闲内存,该容器也会被强制换页,从而把宿主机的 swap 用量拉高。

NUMA 架构下,“物理内存还有空余”未必每个节点都有空余。进程绑定在某个 CPU 节点上运行时,如果那个节点内存已经吃紧,另一个节点空有富余内存,内核依然可能对本节点内存执行换出。可以通过numactl --hardware和numastat观察节点间的内存分配情况。这类问题在大内存物理机上更容易碰到。

3. 定位到底是谁把 swap 占了

3.1 用脚本遍历 /proc,揪出 VmSwap 最大的进程

要找出哪个进程在消耗 swap,Linux 给每个进程的/proc/<pid>/status文件里暴露了VmSwap字段,单位是 KB。可以一条命令全部遍历排序,找出 Top 占用者。

for pid in $(ls /proc | grep -E '^[0-9]+$'); do swap=$(awk '/VmSwap/ {print $2}' /proc/$pid/status 2>/dev/null) if [ -n "$swap" ] && [ "$swap" -gt 0 ]; then cmd=$(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null | cut -c1-100) echo "${swap}KB ${pid} ${cmd}" fi done | sort -rn | head -20

输出大概是这样的:

3684436KB 2841 /usr/lib/jvm/java/bin/java -Xmx4000m ... 1048572KB 3156 /usr/bin/python3 /opt/app/server.py

建议用 root 运行,否则一些受保护进程的状态文件读不到。看到的结果会比你预期的更“反直觉”:有时候 swap 占用最多的不是数据库,而是一个配置了超大堆内存的 Java 进程,或者某个不太起眼的日志采集程序。

脚本拿到之后,需要结合进程的真实情况判断。是进程内存漏了,还是正常的冷数据缓存?我一般用/usr/bin/time -v或监控工具看 RSS、VSZ 的变化趋势,连续观察几天,比单纯看一次快照更有说服力。

3.2 用 smem 和 SwapCached 做交叉确认

如果smem已安装,看进程 swap 占用会更方便:

smem -rs swap | head -20

smem -r按内存排序,s表示按 swap 排序并打印 swap 列。没有装的话,大多数发行版可以通过包管理器安装,属于很小巧的辅助工具。

再看一眼SwapCached:

grep SwapCached /proc/meminfo

SwapCached表示已经换出、但同时又保留了文件缓存关联的那部分页面。它说明发生过换出动作,但这部分内容并不一定都留在 swap 里,也可能已经被重新读入内存。如果 swap 占用高而 SwapCached 很低,说明换出的页面没有被访问过,冷数据一直躺在 swap 里。

3.3 检查 tmpfs、zram 造成的“假 swap 占用”

有些地方看着 swap 占用高,其实根本不是磁盘 swap。典型场景是/dev/shm这类 tmpfs。它占用的是内存,但在df里看到的是挂载点容量,并不会直接反映到 free 的 swap 列。

另一种情况是用了 zram 或 zswap 做压缩内存交换。free的 Swap 列显示的是 zram 的压缩内存大小,而不是真实磁盘。如果系统启用了 zram,zramctl能看到设备状态,free里的 Swap 高并不代表磁盘被写了多少,IO 压力也没那么大。很多新式安装镜像默认会启用 zram,看到 swap 高别急着慌,先确认是不是 zram 设备。

3.4 判断“占着”还是“正在用”

结合前面提到的vmstat的si/so和/proc/vmstat里的pswpin、pswpout累计值:

grep pswp /proc/vmstat

如果pswpin、pswpout累计值变化不大,说明 swap 里的页面只是“静态占用”,没有频繁读写。这种情况下即便占用比例高,对性能的实际影响也有限。

反过来,如果pswpout持续增大,就意味着不断有内存页被换出,属于真实的内存压力信号。此时的重点不是“释放 swap”,而是“减少内存占用”或者“增加物理内存”。

4. 紧急释放与长期调优的实操方案

4.1 安全释放 swap 的完整操作顺序

确认了 swap 里确实是冷数据、物理内存 available 也足够,才考虑手动释放。很多人一上来就swapoff -a,这是最容易翻车的动作。因为swapoff会把 swap 里的所有页面一次性读回物理内存,如果 available 不足以容纳这些页面,内核就会进入直接内存回收,更严重时可能触发 OOM Killer。

我建议按这个顺序操作:

# 1. 先把内核对 swap 的偏好降下来,避免刚释放完又立刻写回 sysctl -w vm.swappiness=10 # 2. 同步并清理页缓存、目录项、inode,先腾出可回收内存 sync echo 3 > /proc/sys/vm/drop_caches # 3. 确认 available 内存足够容纳 swap 里已使用的量 free -h # 4. 停用并重新启用 swap swapoff -a swapon -a # 5. 观察确认是否回到正常范围 free -h vmstat 1 5

第 4 步的swapoff -a会把所有 swap 设备都停掉,swapon -a再按/etc/fstab里配置重新挂载。如果只想释放某个特定 swap 分区或 swap 文件,则先看swapon --show确认设备路径,再单独对那个设备执行swapoff /dev/mapper/xxx。

释放过程中最好开一个终端持续观察:

watch -n1 'free -h; vmstat 1 2 | tail -5'

如果负载突然升高,si或so飙升,说明内存吃紧,一轮swapoff可能要让系统卡一阵。这个时候不要强行中断swapoff,否则设备状态会处于不一致的中间态。

4.2 调整 swappiness 并让它永久生效

永久修改 swappiness 有两种常用方式。一种是直接写/etc/sysctl.conf(或/etc/sysctl.d/99-swap.conf):

echo 'vm.swappiness=10' > /etc/sysctl.d/99-swap-tuning.conf sysctl --system

另一种是用sysctl -w临时生效后让下次重启后依然保留,但建议直接写配置文件更省心。

关于 swappiness 具体设多少,没有绝对答案,我的经验值如下:

场景建议值理由
高并发 Java / 数据库服务10 左右尽量保留物理内存给热点数据和缓存
普通虚拟机/云主机10~30避免 swap 干扰 IO,同时保留一定降级能力
物理内存极小的设备60 或更高没有足够内存时,swap 是防御 OOM 的缓冲
桌面日常使用20 左右兼顾交互流畅性和内存回收

需要特别提醒:千万不要为了“彻底不用 swap”就把 swappiness 设为 0。0 只是“尽量不用”,不代表绝对不用。当内存压力实在无法通过回收文件页缓解时,内核还是会换出匿名页。把值压到 0 还可能导致另一个问题:内核在内存紧张时缺少 swap 缓冲,触发 OOM Killer 的概率反而增加。

4.3 swapoff 失败怎么处理

释放 swap 时最常见的报错是:

swapoff: swapoff failed: Cannot allocate memory

这句话说明系统当前实际拿不出足够的可分配内存来承载 swap 里的页面。处理顺序我建议这样:

  1. 确认有没有大体积的 tmpfs 在占用内存,df -h /dev/shm之类。
  2. 再看一下slabtop,排除内核 slab 占用过高的情况。slab 里有些页是不可回收的,会造成 swapoff 反复失败。
  3. 把脏页比例临时调低,让内核尽快把脏页写回,从而释放更多可回收内存。dirty_ratio、dirty_background_ratio这类参数需要小心调整,不能猛降,否则会影响写性能。
  4. 用第 4.1 节的脚本排查大进程,如果发现某个进程的 VmSwap 持续增长,先处理进程层面的问题。
  5. 实在不行,低峰期重启服务或整机,再把逻辑理顺。

实操里有一条吐槽点:swapoff 是“全有或全无”的操作,不是按比例释放,也无法指定“释放 50%”。一旦可用内存不够就是失败。所以如果服务器内存压力很大,不要反复尝试硬 swapoff,先治本。

4.4 定期释放脚本:低峰期自动化

如果确认要定期释放,可以写一个简单脚本放到 crontab 里,让它在凌晨低峰期自动判断并释放:

#!/usr/bin/env bash # /opt/scripts/refresh_swap.sh SWAPPINESS_BEFORE=$(cat /proc/sys/vm/swappiness) SWAP_USED=$(free -m | awk '/Swap/ {print $3}') SWAP_TOTAL=$(free -m | awk '/Swap/ {print $2}') AVAILABLE_MEM=$(free -m | awk '/Mem:/ {print $7}') # 阈值:swap 使用超过 20% 才考虑释放 if [ "$SWAP_TOTAL" -lt 1000 ]; then exit 0 fi if [ "$((SWAP_USED * 100 / SWAP_TOTAL))" -lt 20 ]; then exit 0 fi # 至少要留下 2GB 的 available 余量 if [ "$AVAILABLE_MEM" -lt 2048 ]; then exit 0 fi sysctl -w vm.swappiness=10 sync echo 3 > /proc/sys/vm/drop_caches swapoff -a swapon -a sysctl -w vm.swappiness=$SWAPPINESS_BEFORE

脚本里保存了操作前的 swappiness,释放完再恢复原值,避免破坏业务期配置。crontab 示例:

0 4 * * * /bin/bash /opt/scripts/refresh_swap.sh >/tmp/refresh_swap.log 2>&1

要不要定时执行这个脚本?我的态度是:如果问题是配置策略导致,调完 swappiness 就不需要再频繁释放;如果问题会反复出现,那说明内存确实不够用,脚本只是缓解症状,不该长期依赖。

5. 长期防范:监控、告警与场景化调优

5.1 加一条 swap 告警,但别只看占用比例

手动排查只能解决一时,长期还得靠监控。如果监控环境用的是 Prometheus 加 node_exporter,swap 相关指标有两个:

  • node_memory_SwapTotal_bytes:总 swap 容量。
  • node_memory_SwapFree_bytes:剩余 swap 容量。

计算使用率可以用 PromQL 表达式:

(1 - node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes) * 100 > 80

但光看使用率会漏掉“实际换页速率”这个关键信号。建议加上rate(node_vmstat_pswpin[5m])和rate(node_vmstat_pswpout[5m])这类换页速率指标。如果 swap 占用很高但换页速率为 0,是静态问题;如果换页速率持续上升,说明动态内存压力已经出现,需要尽快处理。

用传统监控或云监控时,也可以自己做脚本采集。比如把下面的检查放进 cron:

#!/usr/bin/env bash SWAP_USED=$(free -m | awk '/Swap/ {print $3}') SWAP_TOTAL=$(free -m | awk '/Swap/ {print $2}') if [ "$SWAP_TOTAL" -ne 0 ]; then PCT=$((SWAP_USED * 100 / SWAP_TOTAL)) if [ "$PCT" -gt 80 ]; then echo "$(date) swap usage ${PCT}%" >> /var/log/swap_monitor.log fi fi

5.2 数据库和 Java 服务的专项调整建议

如果服务器跑的是数据库或 Java 应用,除了调内核参数,还要看应用自身的堆参数和缓存池配置。Java 进程把堆设得太大,远超物理内存,又没配置合理的 GC 策略,OOM 和 swap 都很难根治。最稳妥的方式是让 JVM 堆峰值不超过物理内存的 60%~70%,留下足够的内存给系统缓存和页表。

数据库类服务也要为innodb_buffer_pool_size、shared_buffers这类缓存参数留预算。很多 DBA 喜欢把缓冲池设成物理内存的 70%~80%,结果剩余内存遇到高峰流量时不够用,内核只能从 swap 里找补。

我的习惯是:先把应用内存预算画出来,再设置 swappiness,最后才提“释放 swap”的事。顺序反了,就是天天做着“外科手术”,却不去治病因。

5.3 要不要干脆关闭 swap

这是每次讨论都会吵起来的问题。我的立场是:不是所有机器都适合关闭 swap。

如果这台机器是云主机,物理内存在 32G 以上且业务内存需求平稳,关闭 swap 可以减少磁盘 IO 抖动,降低不确定性。前提是你要有足够的监控和 OOM 告警,因为一旦出现内存尖峰,内核没有 swap 缓冲,会直接触发 OOM Killer。

如果这台机器只有 4G 或 8G 内存,或者跑着内存波动很大的批处理任务,我建议保留少量 swap,并调低 swappiness。比如内存 16G、磁盘是 SSD,swap 设 4G 就够,没必要按“两倍物理内存”这种老规矩来。swap 的定位是“保险杠”,不是“仓库”。

关闭 swap 的操作很简单,但要考虑开机后是否重新挂载。如果/etc/fstab里有 swap 条目,需要先注释掉,然后执行swapoff -a。不然重启后又回来了。

5.4 容器平台上的 swap 策略

容器引擎宿主机上,推荐把宿主机的 swappiness 调低,同时对容器的 swap 上限做约束。如果容器平台允许,最好在容器资源限制中显式设置内存和 swap 比例。容器不是越大越好,swap 也不是无限越好。给每个容器设置过大的 swap,只会让宿主机磁盘悄悄变成“内存垃圾场”,问题积累到一定程度再爆发。

6. 几个常见的坑,还有我的体会

6.1 直接 swapoff 可能会杀死关键进程

我见过有人为了清 swap,直接在业务高峰期执行swapoff -a。结果 swap 里的页面全部涌回内存,加上当前业务本身还在分配内存,系统瞬间进入内存压力峰值,内核直接启动 OOM Killer,把正在跑的数据库进程给杀了。从那以后我给自己定了一条规矩:swapoff 前必须确认 available 内存足够容纳 swap 使用量,否则宁可不动。

6.2 swappiness 调成 0 不等于“禁用 swap”

很多教程说“让系统不使用 swap 就把 swappiness 调为 0”,这其实有误导。当内存压力超过文件页可回收的极限时,内核依然会使用 swap。把 swappiness 设为 0 的副作用是:系统可能在 swap 和内存之间缺乏平滑过渡,一旦突发内存压力,容易直接进入直接回收或 OOM。所以我更倾向于 10,而不是 0。

6.3 swap 高,也可能是“假象”

我自己排查过一台机器,swap 用了几十 G,所有人都说内存不够,要加内存。最后发现它启用了 zram,swap 占用的是压缩内存,磁盘 IO 根本没多少变化。所以看到一个异常高的 swap 数值,先查zramctl、/sys/block/zram*,再下结论。这个步骤简单却能省掉很多无谓的加配置操作。

6.4 真正的长期方案永远是“内存规划”

调 swappiness、释放 swap、设置告警,这些都是缓解手段。如果我连续几天监控发现 swap 换页速率一直降不下来,或者 swap 占用反复冲到高位,那真正的答案只有一个:这台机器的物理内存规划不合理,需要扩容或者优化应用内存占用。swap 可以当止痛药,不能当饭吃。

关于 swap 排查,最后再分享一个我自己的习惯:每次处理完类似问题,我都会顺手把free -h的 available 列、vmstat的 si/so、以及最大的 VmSwap 进程记录到当天的运维笔记里。下次再遇到,先对比历史数据,往往一眼就能看出是新问题还是老毛病复发。这套现场保存的习惯,比任何命令都值钱。

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

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

立即咨询