快速定位高内存进程和 Swap 使用者
在 Linux 服务器上排查内存问题时,经常会遇到以下情况:
free -h显示可用内存不足;- Swap 已经使用了数 GiB;
ps中却没有发现特别夸张的进程;- 某个进程当前 RSS 不高,但曾经换出了大量内存;
- 需要进一步确认进程属于哪个服务或 Docker 容器。
本文整理一套常用的排查命令,并以一次实际问题为例,说明如何从系统内存总览逐步定位到具体进程和容器。
一、快速查看内存与 Swap 总体情况
首先查看系统当前的内存使用情况:
free -h
典型输出如下:
total used free shared buff/cache available
Mem: 31Gi 20Gi 1.2Gi 500Mi 10Gi 9.5Gi
Swap: 8.0Gi 3.2Gi 4.8Gi
重点关注以下字段:
used:当前已使用的内存;buff/cache:文件缓存和部分内核缓存;available:在不触发大量 Swap 的情况下,应用程序大致还能使用的内存;Swap used:已经被换出到 Swap 的内存总量。
不要只看 free 字段判断内存是否不足。Linux 会尽可能利用空闲内存作为缓存,因此 available 通常比 free 更有参考价值。
查看当前启用的 Swap 设备:
swapon --show
示例:
NAME TYPE SIZE USED PRIO
/dev/sda3 partition 8G 3.2G -2
也可以查看更详细的 Swap 信息:
cat /proc/swaps
二、按 RSS 排序:查找当前最占物理内存的进程
可以使用 ps 按内存占用比例降序排列:
ps aux --sort=-%mem | head -20
也可以直接按 RSS 排序:
ps -eo pid,ppid,user,%mem,rss,vsz,stat,comm,args \
--sort=-rss | head -20
其中:
%MEM:进程 RSS 占系统物理内存的比例;RSS:当前驻留在物理内存中的页面大小,单位通常为 KiB;VSZ:进程的虚拟地址空间大小;COMMAND或ARGS:进程名称及完整启动参数。
需要注意,RSS 表示进程当前驻留在物理内存中的页面,其中可能包含与其他进程共享的内存,因此不能简单地把所有进程的 RSS 相加并认为这就是系统实际使用的物理内存。
如果只是快速查找“当前最占内存”的进程,RSS 排序已经足够实用。
三、按 Swap 排序:定位真正使用 Swap 的进程
这是排查 Swap 占用时最关键的一步。
ps aux 默认不会直接显示每个进程使用了多少 Swap。可以遍历 /proc/<pid>/status,读取其中的 VmSwap 字段。
for pid in /proc/[0-9]*; do
pid=${pid##*/}
swap=$(awk '/^VmSwap:/{print $2}' "/proc/$pid/status" 2>/dev/null)
if [ -n "$swap" ] && [ "$swap" -gt 0 ]; then
comm=$(cat "/proc/$pid/comm" 2>/dev/null)
printf "%12d kB %-30s [pid %s]\n" "$swap" "$comm" "$pid"
fi
done | sort -rn | head -20
输出示例:
3145728 kB kit_spare [pid 18342]
524288 kB java [pid 9281]
131072 kB mysqld [pid 2165]
这表示:
kit_spare使用了约 3 GiB Swap;java使用了约 512 MiB Swap;mysqld使用了约 128 MiB Swap。
可以将结果直接转换成 MiB,阅读起来更直观:
for pid in /proc/[0-9]*; do
pid=${pid##*/}
swap=$(awk '/^VmSwap:/{print $2}' "/proc/$pid/status" 2>/dev/null)
if [ -n "$swap" ] && [ "$swap" -gt 0 ]; then
comm=$(cat "/proc/$pid/comm" 2>/dev/null)
printf "%10.2f MiB %-30s [pid %s]\n" \
"$(awk -v kb="$swap" 'BEGIN {print kb / 1024}')" \
"$comm" \
"$pid"
fi
done | sort -rn | head -20
本次问题就是通过这一方法发现:
kit_spare 使用了约 3 GiB Swap
随后继续检查该进程,最终确认它属于 Collabora Online 服务。
为什么使用 /proc/<pid>/status
/proc/<pid>/status 中的 VmSwap 是内核提供的单进程 Swap 统计信息,是定位进程 Swap 使用量最直接的数据来源之一。
查看单个进程:
grep -E 'Name|Pid|PPid|VmRSS|VmSwap' /proc/<pid>/status
例如:
grep -E 'Name|Pid|PPid|VmRSS|VmSwap' /proc/18342/status
需要注意,VmSwap 更适合用于定位和排序,不一定能与 free 显示的系统 Swap 使用量完全一一对应。线程、共享页面以及内核统计口径可能造成一定差异。
四、使用 smem 查看 RSS、PSS 和 Swap
如果系统已经安装 smem,可以更直观地查看进程内存。
按 Swap 排序:
smem --sort=swap -r | head -20
按 RSS 排序:
smem --sort=swap -r | head -20
按 PSS 排序:
smem --sort=pss -r | head -20
smem 常见字段包括:
USS:进程独占的物理内存;PSS:将共享内存按共享进程数量进行分摊;RSS:当前驻留在物理内存中的总页面;Swap:进程使用的 Swap。
与 RSS 相比,PSS 更适合估算一个进程对系统物理内存的真实贡献。
常见安装方式:
# Debian / Ubuntu
apt install smem
# RHEL / Rocky Linux / AlmaLinux
dnf install smem
五、常用内存工具对比
| 工具 | 能否查看进程 Swap | 支持排序 | 是否需要安装 | 适用场景 |
|---|---|---|---|---|
free | 只能看总量 | 否 | 否 | 查看系统内存和 Swap 总览 |
ps | 默认不能 | 是 | 否 | 查找当前 RSS 较高的进程 |
/proc 遍历 | 是 | 是 | 否 | 精确定位进程 Swap 使用量 |
smem | 是 | 是 | 通常需要 | 同时分析 USS、PSS、RSS 和 Swap |
top | 部分支持 | 交互式 | 否 | 实时观察进程变化 |
htop | 可配置显示 | 交互式 | 通常需要 | 更直观的交互式排查 |
在没有额外工具的服务器上,推荐优先使用:
free + ps + /proc
这套组合不依赖额外软件,适合生产环境快速排查。
六、确认可疑进程的完整信息
找到可疑 PID 后,首先查看进程完整命令行:
cat /proc/<pid>/cmdline | tr '\0' ' '
echo
例如:
cat /proc/18342/cmdline | tr '\0' ' '
echo
也可以使用 ps:
ps -p <pid> -o pid,ppid,user,lstart,etime,%cpu,%mem,rss,vsz,stat,args
查看进程的可执行文件:
readlink -f /proc/<pid>/exe
查看进程当前工作目录:
readlink -f /proc/<pid>/cwd
查看进程打开的文件:
lsof -p <pid>
查看进程环境变量:
tr '\0' '\n' < /proc/<pid>/environ
进程环境变量可能包含密码、令牌或连接信息,生产环境中应谨慎查看和传播。
七、通过父进程判断进程来源
查看目标进程及其父进程:
ps -o pid,ppid,user,stat,comm,args -p <pid>
查看完整进程树:
pstree -aps <pid>
例如:
pstree -aps 18342
如果进程位于 Docker 容器内,进程树中可能出现:
systemd
└─dockerd
└─containerd-shim
└─kit_spare
不过在某些环境中,容器进程会直接显示为宿主机上的普通进程,因此还需要结合 cgroup 信息进一步确认。
八、定位进程属于哪个 Docker 容器
1. 查看进程 cgroup
cat /proc/<pid>/cgroup
过滤常见容器关键字:
cat /proc/<pid>/cgroup | grep -E 'docker|containerd|kubepods'
在 cgroup v1 环境中,可能看到:
10:memory:/docker/7ac55dc3cbe1...
在 cgroup v2 环境中,可能看到:
0::/system.slice/docker-7ac55dc3cbe1.scope
从中提取 Docker 容器 ID:
container_id=$(
sed -nE \
's#.*docker[-/]([0-9a-f]{12,64})(\.scope)?$#\1#p' \
/proc/<pid>/cgroup |
head -1
)
echo "$container_id"
然后查询容器:
docker ps --no-trunc --filter "id=$container_id"
查看容器详细信息:
docker inspect "$container_id"
2. 通过宿主机 PID 反查容器
也可以遍历运行中的容器,比较容器的宿主机 PID:
for cid in $(docker ps -q); do
container_pid=$(docker inspect -f '{{.State.Pid}}' "$cid")
if [ "$container_pid" = "<pid>" ]; then
docker inspect -f \
'容器={{.Name}} 镜像={{.Config.Image}} PID={{.State.Pid}}' \
"$cid"
fi
done
不过需要注意,目标 PID 不一定是容器的主进程 PID。它也可能是容器主进程派生出来的子进程,因此通过 cgroup 反查通常更加可靠。
3. 查看所有容器资源占用
docker stats --no-stream
输出包括:
- CPU 使用率;
- 内存使用量;
- 内存限制;
- 网络收发;
- 磁盘 I/O;
- 进程数量。
docker stats 适合观察容器整体资源使用,但不能直接说明容器内具体哪个进程使用了 Swap。
九、通过 Docker Label 定位 Compose 文件
如果容器由 Docker Compose 启动,Docker 通常会自动添加 Compose Label。
先找到容器 ID,然后执行:
docker inspect <container_id> \
--format '{{json .Config.Labels}}'
重点关注以下标签:
com.docker.compose.project
com.docker.compose.service
com.docker.compose.project.working_dir
com.docker.compose.project.config_files
可以直接格式化输出:
docker inspect <container_id> --format '
容器名称:{{.Name}}
镜像:{{.Config.Image}}
Compose 项目:{{index .Config.Labels "com.docker.compose.project"}}
Compose 服务:{{index .Config.Labels "com.docker.compose.service"}}
工作目录:{{index .Config.Labels "com.docker.compose.project.working_dir"}}
配置文件:{{index .Config.Labels "com.docker.compose.project.config_files"}}
'
示例:
容器名称:/collabora
镜像:collabora/code:latest
Compose 项目:nextcloud
Compose 服务:collabora
工作目录:/opt/nextcloud
配置文件:/opt/nextcloud/docker-compose.yml
通过这些 Label,可以快速定位容器对应的 Compose 项目和配置文件。
十、检查内核内存:Slab、页表和缓存
如果进程 RSS 和 Swap 都不足以解释系统内存占用,还需要检查内核内存。
查看关键内存指标:
grep -E \
'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapCached|AnonPages|Slab|SReclaimable|SUnreclaim|PageTables|KernelStack' \
/proc/meminfo
重点字段包括:
AnonPages:匿名内存,通常来自进程堆、栈等;Cached:文件页缓存;SwapCached:已经写入 Swap,但仍保留在物理内存中的页面;Slab:内核对象缓存总量;SReclaimable:理论上可以回收的 Slab;SUnreclaim:不容易回收的 Slab;PageTables:进程页表占用;KernelStack:内核栈占用。
查看 Slab 详细分项:
slabtop -o
只查看前 20 行:
slabtop -o | head -20
也可以直接查看 /proc/slabinfo:
head -20 /proc/slabinfo
如果 Slab 特别大,可以通过 slabtop 查找以下常见对象:
dentry;inode_cache;xfs_inode;ext4_inode_cache;kmalloc-*;- 网络连接跟踪相关缓存。
十一、查看实时换页活动
Swap 已经使用,并不代表系统当前仍在频繁换页。
Linux 可能在之前的内存压力期间将不活跃页面换出;即使后来内存已经释放,这些页面也不会立即自动换回物理内存。
因此,还应观察 si 和 so:
vmstat 1
示例:
procs -----------memory---------- ---swap-- -----io----
r b swpd free buff cache si so
1 0 3145728 524288 131072 8388608 0 0
其中:
si:每秒从 Swap 读入物理内存的数据量;so:每秒从物理内存写入 Swap 的数据量。
如果 Swap 已使用 3 GiB,但 si 和 so 长期为 0,说明系统当前没有明显的换页压力。
如果 si、so 持续较高,同时伴随:
- 系统响应缓慢;
- 磁盘 I/O 增加;
- load average 上升;
- 进程出现
D状态;
则说明系统可能正在发生频繁换页,甚至出现内存抖动。
十二、查看系统 Swap 策略
查看当前 swappiness:
sysctl vm.swappiness
或者:
cat /proc/sys/vm/swappiness
vm.swappiness 用于影响内核使用 Swap 的积极程度。
常见范围为:
0 ~ 200
具体范围和行为可能因内核版本而异。
不要因为看到 Swap 被使用,就立即把 vm.swappiness 设置为 0。更合理的处理顺序是:
- 确认是否存在持续换页;
- 找到主要使用 Swap 的进程;
- 分析进程是否存在异常内存增长;
- 检查容器或服务的内存限制;
- 最后再考虑是否需要调整内核参数。
临时调整示例:
sysctl -w vm.swappiness=10
永久配置可写入:
/etc/sysctl.d/99-memory.conf
内容:
vm.swappiness = 10
然后加载:
sysctl --system
十三、本次问题的完整排查思路
本次排查的核心链路如下:
free -h
│
├─ 查看系统内存和 Swap 使用总量
│
▼
ps aux --sort=-%mem
│
├─ 查找当前 RSS 较高的进程
│
▼
遍历 /proc/<pid>/status 中的 VmSwap
│
├─ 查找真正使用 Swap 的进程
│
└─ 发现 kit_spare 使用约 3 GiB Swap
│
▼
cat /proc/<pid>/cmdline
pstree -aps <pid>
│
├─ 确认进程启动参数和父进程
│
▼
cat /proc/<pid>/cgroup
│
├─ 提取 Docker 容器 ID
│
▼
docker inspect
│
├─ 确认 kit_spare 属于 Collabora Online
│
▼
查看 Docker Compose Labels
│
└─ 找到对应的 Compose 工作目录和配置文件
精简后的命令链如下:
# 1. 查看内存与 Swap 总览
free -h
swapon --show
# 2. 查看当前 RSS 最大的进程
ps aux --sort=-%mem | head -20
# 3. 查看 Swap 最大的进程
for pid in /proc/[0-9]*; do
pid=${pid##*/}
swap=$(awk '/^VmSwap:/{print $2}' "/proc/$pid/status" 2>/dev/null)
[ -n "$swap" ] && [ "$swap" -gt 0 ] &&
printf "%12d kB %-30s [pid %s]\n" \
"$swap" \
"$(cat "/proc/$pid/comm" 2>/dev/null)" \
"$pid"
done | sort -rn | head -20
# 4. 查看可疑进程
ps -p <pid> -o pid,ppid,user,lstart,etime,%cpu,%mem,rss,vsz,stat,args
cat /proc/<pid>/cgroup
# 5. 查看容器资源
docker stats --no-stream
# 6. 根据容器 ID 查看 Compose 信息
docker inspect <container_id> --format '
容器={{.Name}}
镜像={{.Config.Image}}
项目={{index .Config.Labels "com.docker.compose.project"}}
服务={{index .Config.Labels "com.docker.compose.service"}}
工作目录={{index .Config.Labels "com.docker.compose.project.working_dir"}}
配置文件={{index .Config.Labels "com.docker.compose.project.config_files"}}
'
十四、结论
排查 Linux 内存问题时,应该区分以下几个概念:
- 系统使用了多少内存;
- 当前哪个进程的 RSS 最大;
- 哪个进程使用了最多 Swap;
- 系统当前是否还在频繁换页;
- 内存是否被文件缓存或 Slab 占用;
- 可疑进程属于哪个系统服务或容器。
一套实用的排查顺序是:
free
→ ps
→ /proc/<pid>/status
→ vmstat
→ 进程命令行和进程树
→ cgroup
→ docker inspect
→ slabtop
其中,遍历 /proc/<pid>/status 中的 VmSwap,是定位“谁占用了 Swap”最关键的一步。
同时需要避免一个常见误区:
Swap 已经被使用,不等于系统当前内存不足。
只有结合 MemAvailable、vmstat 中的 si/so、磁盘 I/O 和应用响应情况,才能判断服务器是否正在遭受真正的内存压力。