快速定位高内存进程和 Swap 使用者

快速定位高内存进程和 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:进程的虚拟地址空间大小;
  • COMMANDARGS:进程名称及完整启动参数。

需要注意,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 可能在之前的内存压力期间将不活跃页面换出;即使后来内存已经释放,这些页面也不会立即自动换回物理内存。

因此,还应观察 siso

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,但 siso 长期为 0,说明系统当前没有明显的换页压力。

如果 siso 持续较高,同时伴随:

  • 系统响应缓慢;
  • 磁盘 I/O 增加;
  • load average 上升;
  • 进程出现 D 状态;

则说明系统可能正在发生频繁换页,甚至出现内存抖动。


十二、查看系统 Swap 策略

查看当前 swappiness

sysctl vm.swappiness

或者:

cat /proc/sys/vm/swappiness

vm.swappiness 用于影响内核使用 Swap 的积极程度。

常见范围为:

0 ~ 200

具体范围和行为可能因内核版本而异。

不要因为看到 Swap 被使用,就立即把 vm.swappiness 设置为 0。更合理的处理顺序是:

  1. 确认是否存在持续换页;
  2. 找到主要使用 Swap 的进程;
  3. 分析进程是否存在异常内存增长;
  4. 检查容器或服务的内存限制;
  5. 最后再考虑是否需要调整内核参数。

临时调整示例:

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 已经被使用,不等于系统当前内存不足。

只有结合 MemAvailablevmstat 中的 si/so、磁盘 I/O 和应用响应情况,才能判断服务器是否正在遭受真正的内存压力。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注