系统变慢时,先区分 CPU 忙、任务在等待、内存紧张、磁盘空间不足,还是网络连接异常。不同工具观察的是不同层次:top 展示资源概况,ps 定位进程与线程,ss 观察套接字,strace 跟踪系统调用。单个指标只能缩小范围,需要同一时间窗口内的其他证据支撑判断。

本文面向 Linux,主要采用 procps-ng、GNU coreutils、iproute2 的命令格式。macOS 的选项和字段不同,也没有 Linux 的 /proc 接口;精简容器可能缺少部分命令。示例中的 PID 12466、端口 8080 和路径 /var/log 均需替换为实际目标。输出字段随工具版本变化,应以本机 man 为准。

从现象选择排查入口

先记录时间、目标 PID、进程启动时间和所在容器,再连续采样。快照适合确认“现在是什么”,区间统计适合回答“这几秒发生了什么”。进程重启后 PID 可能复用,不能把重启前后的数据直接拼起来。

图 1:从系统现象逐步定位到进程证据。

flowchart TD
    accTitle: 从系统现象到进程证据的排查路径
    A["延迟升高或服务异常"] --> B["uptime / top / free / df<br/>确认资源概况"]
    B --> C{"主要异常在哪"}
    C --> D["CPU 或 load<br/>top -H / vmstat"]
    C --> E["内存<br/>free / status / smaps"]
    C --> F["空间或 I/O<br/>df / du / iostat / iotop"]
    C --> G["连接或端口<br/>ss / lsof"]
    D --> H["定位 PID、TID 与采样时间"]
    E --> H
    F --> H
    G --> H
    H --> I["必要时短时 strace<br/>与应用日志交叉验证"]

可以从这组低成本快照开始:

1
2
3
4
5
6
date -Is
uptime
free -h
df -h
df -i
vmstat 1 5

vmstat 的第一份报告中,CPU 和速率统计通常是启动以来的平均值;后续报告才对应采样间隔,进程数和内存字段则是瞬时值。支持 -y 的版本可用 vmstat -y 1 5 跳过首份报告。不要把第一行与后续区间当成同一种数据。vmstat 手册

进程与线程:ps、pstree

确认进程身份和父子关系

1
2
3
4
ps aux
ps -elf
ps -eo pid,ppid,lstart,stat,pcpu,pmem,args --forest
pstree -lap

ps aux 使用 BSD 风格选项;不要混写成容易产生歧义的 ps -aux。PPID 表示父进程 ID,--forest 的缩进显示父子关系。nohup 主要改变挂断信号的处理方式,缩进不是“使用 nohup 启动”的标志。ps 手册

例如下面的示意树中,1、35、42 是进程,花括号中的 43、44 是进程 42 的线程。实际显示方式受 pstree 选项和实现影响。

1
2
3
4
5
docker_run.sh,1
└─start.sh,35
└─app1,42
├─{app1},43
└─{app1},44

图 2:父子进程关系与进程内线程的区别。

flowchart TD
    accTitle: 父子进程与进程内线程
    A["PID 1<br/>容器入口"] --> B["PID 35 / PPID 1<br/>启动脚本"]
    B --> C["PID 42 / PPID 35<br/>应用进程"]
    C -. "同属一个线程组" .-> D["TID 42<br/>主线程"]
    C -. "同属一个线程组" .-> E["TID 43<br/>工作线程"]
    C -. "同属一个线程组" .-> F["TID 44<br/>工作线程"]

下钻到线程和等待状态

1
2
3
ps -L -p 12466 -o pid,tid,psr,stat,pcpu,comm
ps -p 12466 -o pid,ppid,stat,wchan:32,args
top -H -p 12466
状态 含义 后续观察
R 正在运行或等待 CPU 结合 CPU 利用率、运行队列和线程采样
S 可中断睡眠 常见于等待事件,单独出现不代表异常
D 不可中断睡眠 结合等待点与 I/O,不能直接断言磁盘损坏
T / t 停止或被跟踪 检查信号与调试状态
Z 已退出但尚未被父进程回收 检查父进程的子进程回收逻辑

进程级 CPU 异常可以继续缩小到 TID,但操作系统线程 ID 不等于语言运行时中的线程名称。关联 JVM 线程栈时,还需要核对线程标识及其进制表示。

CPU 与负载:top、uptime、vmstat

load average 不是 CPU 利用率

uptime 与 top 中的三个 load average 分别对应 1、5、15 分钟的负载平均值。在 Linux 中,负载统计可运行任务和不可中断睡眠任务,包含线程;它不是每分钟处理的进程数,也不能换算成 CPU 利用率。uptime 手册

4 个逻辑 CPU 的机器,如果负载主要来自可运行任务,持续接近 4 可以提示 CPU 并发需求接近可用容量;如果其中大量任务处于 D 状态,同样的 load 可能伴随较低 CPU 利用率。容器还需考虑 CPU 配额与亲和性,宿主机核数不一定等于容器可用算力。

图 3:同样的高 load 可以对应不同的 CPU 使用情况。

flowchart TD
    accTitle: Linux 负载构成与 CPU 时间的区别
    A["Linux load average"] --> B["R:运行中或等待 CPU"]
    A --> C["D:不可中断睡眠"]
    B --> D["与可用 CPU 容量比较<br/>观察 vmstat r 和 top"]
    C --> E["检查等待点与 I/O<br/>观察 vmstat b 和设备统计"]
    D --> F["CPU 忙且 r 持续较高<br/>支持 CPU 竞争判断"]
    E --> G["CPU 仍可能空闲<br/>load 高不等于 CPU 忙"]

1 分钟值高于 5、15 分钟值,通常表示近期负载正在上升;反过来可能正在恢复。三个窗口都值得观察,不能只看 15 分钟,也不能因为短时升高就判断“问题不大”。

top 的 CPU 时间如何读

字段 含义 判断边界
us / ni 普通用户态 / 调整过 nice 的用户态时间 需要线程或栈采样定位代码
sy 内核态执行时间 高值可能涉及系统调用等,不能单凭它认定竞态
id 空闲时间 系统汇总值可能掩盖单核繁忙
wa I/O 等待相关的 CPU 时间计账 不是磁盘利用率,多核下计账有局限
hi / si 硬中断 / 软中断处理时间 需进一步查看中断来源
st 虚拟机等待期间被 hypervisor 取走的 CPU 时间 提示虚拟化调度影响,不能直接确定其他虚拟机的行为

top 的进程 %CPU 默认常按单个逻辑 CPU 为 100% 计量,多线程进程可超过 100%;切换归一化显示模式后口径会变化。ps %CPU 通常是进程整个生命周期内的平均值,与 top 刷新间隔内的值不能直接比较。top 手册

wa 的多核归属与可靠性存在限制。低 wa 不能排除单个服务正在等待 I/O,高 wa 也不能独立定位设备瓶颈。内核 proc 文档

vmstat 把队列、换页和 CPU 放在同一时间窗口

1
2
3
4
vmstat 1 5
vmstat -s
# slab 统计,可能需要更高权限
vmstat -m
字段 含义 可关联的证据
r / b 可运行任务数 / 阻塞等待 I/O 完成的任务数 CPU 容量、D 状态及等待点
swpd 已用 swap 大小 free 中的 swap 与进程 VmSwap
si / so 从 swap 换入 / 换出的速率 持续换页、内存压力与延迟
bi / bo 块设备读入 / 写出速率 iostat 的设备统计
in / cs 每秒中断 / 上下文切换次数 与相同负载下的基线比较
us / sy / id / wa / st CPU 时间分类 top 与线程级观察

当前 procps-ng 手册中的 bi/bo 以 KiB/s 计,内存字段和 si/so 的单位还要留意版本及 -S 选项。上下文切换高说明切换频繁,是否有问题取决于工作负载与基线。

内存:free、top、pmap、smaps

系统剩余多少内存,先看 available

1
2
free -h
grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SReclaimable|Shmem|Swap' /proc/meminfo

free 低不一定意味着内存不足。文件缓存和部分内核缓存可以回收;available 是无需交换即可供新应用使用的内存估计,已考虑可回收部分和必要保留量。它不是“缓冲的交换区大小”。free 手册

字段 含义
total / free 可用物理内存总量 / 完全未使用的内存
available 无需交换即可供新应用使用的内存估计
buff/cache 缓冲、页缓存及相关可回收内核缓存
shared 主要对应 tmpfs 等使用的 Shmem,并非所有共享映射的总量
Swap used 已占用的交换空间,不能据此认定当前正在频繁换页

当前 procps-ng 的 used 按 total - available 计算。旧版本曾有不同计算方式,并出现 -/+ buffers/cache 行;解释历史截图时要先确认版本,不能套用同一个公式。

如果 available 持续下降,同时 vmstat si/so 持续活跃并伴随延迟增加,才形成更完整的内存压力证据。容器还需核对自身内存限制,主机有空闲内存不保证容器不会触及上限。

VIRT、RSS、PSS 分别回答什么

指标 观察对象 常见误读
VIRT / VmSize 进程虚拟地址空间大小,包含映射和预留等 不能等同于实际 RAM,也不能套用 VIRT = RES + SWAP
RES / RSS / VmRSS 常驻物理内存,包含常驻的文件映射和共享页 RSS 不是固定以页数表示,各接口单位不同
PSS 将共享页按共享者数量分摊后的常驻量 适合减少跨进程汇总时的重复计算
SHR top 中可能被其他进程共享的常驻部分 不能直接用 RES − SHR 当作精确私有内存
DATA 私有数据等虚拟内存范围,具体分类看版本 不一定已驻留,不能用 RES = CODE + DATA
VmSwap 私有匿名页的换出量,不含 shmem 的 swap 不是“已申请但未使用的空间”

图 4:地址空间、常驻页和共享页分摊的关系。

flowchart TD
    accTitle: 虚拟地址空间、RSS 与 PSS 的计量关系
    A["进程虚拟地址空间 VIRT"] --> B["当前驻留的页面<br/>计入 RES / RSS"]
    A --> C["已换出的匿名页<br/>不计入 RSS"]
    A --> D["未驻留的其他映射<br/>如尚未触页或文件页不在内存"]
    B --> E["私有驻留页<br/>RSS 与 PSS 均全额计入"]
    B --> F["共享驻留页<br/>每个进程 RSS 均全额计入"]
    F --> G["PSS 按共享者数量分摊<br/>4 KiB 页由两进程共享时各计 2 KiB"]

例如,两个进程各有 8 MiB 私有驻留页,并共同映射同一组 4 MiB 驻留共享页,则每个进程 RSS 为 12 MiB、PSS 为 10 MiB。仅在这个简化模型中,两者实际合计使用 20 MiB 物理页,而 RSS 相加会得到 24 MiB。内核 proc 文档

从进程总量下钻到映射

1
2
3
4
5
6
ps -eo pid,pcpu,pmem,rss,vsz,args --sort=-rss | head -n 11
grep -E '^(Name|VmPeak|VmSize|VmHWM|VmRSS|VmSwap|Threads):' /proc/12466/status
pmap -x 12466
# 内核提供时,优先读汇总;需要逐映射定位时再读 smaps
cat /proc/12466/smaps_rollup
cat /proc/12466/smaps

pmap -x 适合查看映射分布;smaps 提供每个映射的 Size、Rss、Pss、共享/私有及干净/脏页信息。smaps_rollup 提供汇总,避免自行累加数百个映射,但读取仍有页表扫描成本,不宜高频轮询。

status 中的 VmHWM 是常驻量峰值,VmPeak 是虚拟内存峰值。VmRSS 等快速统计可能不够精确,需要更细致核算时再用 smaps。仅凭 VmStk 不能得到 JVM 所有线程的本地栈总量。proc_pid_status 手册

排查单个进程 swap 可直接读取它的 VmSwap。跨进程遍历 /proc/*/status 时,进程可能退出,权限也可能不同,结果只是一次近似快照。

磁盘空间与 I/O:df、du、iostat、iotop

df 看文件系统,du 看目录树

1
2
3
4
df -h /var/log
df -i /var/log
du -h --max-depth=1 /var/log | sort -h
du -ah /var/log | sort -hr | head -n 10

df 回答文件系统还有多少可用空间;df -i 检查 inode 是否耗尽。du 遍历目录,默认统计文件占用的磁盘块,du --apparent-size 则按表观大小统计。稀疏文件、硬链接、挂载点和权限都可能影响比较。

sort -h 才能按 K、M、G 等可读单位排序。直接 sort 是字符串排序,grep G 还会漏掉其他单位。上面的递归扫描适合缩小范围后使用,大目录扫描本身也会增加 I/O。

图 5:文件删除后仍被进程打开时,df 与 du 为何可能不一致。

flowchart TD
    accTitle: 已删除但仍打开的文件造成 df 与 du 差异
    A["进程打开日志文件"] --> B["删除最后一个目录链接"]
    B --> C["du 遍历目录<br/>已无法找到这个文件"]
    B --> D["进程仍持有文件描述符"]
    D --> E["文件数据块仍被占用<br/>df 继续计入"]
    E --> F["lsof +L1 查找线索"]
    F --> G["持有者关闭且相关引用释放后<br/>空间才可回收"]
1
2
# 查看链接数小于 1 的打开文件;查看其他用户的进程可能需要权限
lsof +L1

已删除但仍打开的文件只是 df/du 差异的一种来源,还应确认比较的是同一文件系统、相同挂载视图及可访问目录。排查阶段先确认持有者,再决定如何让应用关闭或重新打开文件。GNU 磁盘用量工具说明

空间不足和 I/O 慢是两种问题

df 正常只能说明容量尚有余量,不能证明存储延迟正常。设备级观察可用 sysstat 提供的 iostat,进程级归因可用 iotop。

1
2
3
4
iostat -xz 1 5
sudo iotop -o -P
sudo iotop -a -P
sudo iotop -d 2

iostat 的 await 包含请求排队与服务时间;%util 是设备存在 I/O 活动的时间比例,对并行设备不能直接当作“性能上限百分比”。结合请求量、延迟和队列变化判断,首份报告也要注意启动以来统计的口径。iostat 手册

iotop 的 -o 只显示有活动的任务,-P 按进程展示,-a 观察累计量。I/O 等待百分比等字段依赖内核配置、权限与实现版本;缓存和异步写回也会使进程读写与设备实际读写不同步。iotop 手册

网络:ss、netstat、lsof

分清端口、连接和进程

1
2
3
4
5
6
7
8
9
10
# 监听中的 TCP 端口,保留数字地址和端口
ss -lntp
# 所有 TCP 套接字
ss -antp
# 套接字汇总
ss -s
# 定位监听端口 8080
ss -lntp 'sport = :8080'
# 兼容仍使用 net-tools 的环境
netstat -antp

ss 来自 iproute2,netstat 来自 net-tools;它们不是同一工具的不同名字。-p 能显示多少进程信息取决于权限。容器内看到的套接字还受网络命名空间限制,空结果不等于宿主机没有监听。ss 手册

图 6:从监听端口找到持有套接字的进程。

flowchart TD
    accTitle: 端口、套接字、文件描述符和进程的定位关系
    A["TCP 监听端口 8080"] --> B["ss -lntp<br/>查监听套接字及持有者"]
    B --> C["套接字"]
    C --> D["PID 12466 的 FD 57"]
    D --> E["ps / proc status<br/>确认进程身份"]
    E --> F["lsof -a -p 12466 -i<br/>查看该进程网络文件"]

监听只证明本地套接字处于 LISTEN,不能证明防火墙、路由和应用请求处理都正常。服务可达性仍需连接测试与应用日志支撑。

lsof:PID 不是端口号

1
2
3
4
5
6
# 查端口 8080 的 TCP 监听者
lsof -nP -iTCP:8080 -sTCP:LISTEN
# 查 PID 12466 打开的网络文件
lsof -nP -a -p 12466 -i
# 查 PID 12466 打开的所有文件
lsof -nP -p 12466

lsof -i:12466 查询的是端口 12466,不能因为 jps 输出 PID 12466 就这样使用。-nP 关闭主机名与服务名解析,-a 让 PID 与网络选择条件取交集;省略 -a 可能扩大为多个选择条件的并集。lsof 手册

常用列包括 COMMAND、PID、USER、FD、TYPE 和 NAME。FD 除数字描述符外,也可能是 cwd、txt、mem 等特殊项,因此不能把 lsof 的所有输出行都算作打开的数字 FD。

系统调用:strace

当进程级指标只能说明“在等”或“频繁进入内核”,可用系统调用记录进一步检查文件访问、连接失败和等待操作。

1
2
3
4
5
6
7
8
9
10
# 终端前台短时跟踪,观察结束后按 Ctrl-C 脱离
strace -f -tt -T -p 12466
# 统计系统调用;附加模式同样需要主动结束
strace -f -c -p 12466
# 跟踪文件路径相关调用,可覆盖 openat 等调用
strace -f -e trace=%file -p 12466
# 网络相关调用
strace -f -e trace=%network -p 12466
# 启动时跟踪并写文件
strace -f -tt -T -o /tmp/app.strace -- ./app

-f 用于跟踪子进程,在 Linux 附加多线程进程时也可覆盖其线程;-tt 增加时间戳,-T 显示每次系统调用的经过时间,通常以秒的小数表示。-c 默认汇总的是系统调用 CPU 时间,不能直接当作请求端到端延迟。strace 手册

futex 等待可能是正常线程同步,epoll_wait 可能只是服务空闲,单次 ENOENT 也可能是正常路径探测。要结合持续时间、频率、调用参数和应用日志判断。

附加受同用户权限、ptrace 策略和容器能力约束;跟踪还会改变被观测进程的时序并增加开销。输出可能包含路径、网络数据或业务参数,应控制采样时长和日志访问范围。仅看到系统调用无法定位纯用户态计算热点,CPU 栈采样需使用其他工具。

/proc:从汇总指标回到内核接口

Linux 的 /proc 是内核通过 procfs 暴露的运行态接口,许多文件在读取时生成内容,显示大小为 0 仍可读取。它不是普通磁盘目录;其中部分接口可写,诊断读取与修改内核参数是两种不同操作。

图 7:系统级、进程级和线程级的 /proc 入口。

flowchart LR
    accTitle: procfs 的系统、进程与线程观察入口
    A["/proc"] --> B["系统级"]
    A --> C["/proc/PID"]
    B --> D["loadavg / stat<br/>负载与 CPU"]
    B --> E["meminfo / vmstat<br/>内存与虚拟内存统计"]
    B --> F["diskstats / net/dev<br/>设备与网络统计"]
    C --> G["status / limits / io<br/>状态、限制和 I/O"]
    C --> H["maps / smaps / smaps_rollup<br/>地址映射与内存"]
    C --> I["fd / cwd / exe / root<br/>文件及路径视图"]
    C --> J["task/TID<br/>线程级入口"]

系统级常用入口

路径 内容 读取边界
/proc/cmdline、/proc/version 内核启动参数、内核版本 不等于应用启动参数或完整发行版信息
/proc/cpuinfo、/proc/stat、/proc/loadavg CPU 信息、累计 CPU 计数、负载 cpuinfo 条目数不能直接当作容器可用算力
/proc/meminfo、/proc/vmstat 内存概况、虚拟内存计数 先区分存量和累计计数,再计算区间变化
/proc/diskstats、/proc/net/dev 块设备、网卡统计 计数通常需两次采样做差
/proc/swaps、/proc/partitions、/proc/mounts 交换设备、分区和挂载 注意命名空间与挂载视图
/proc/filesystems、/proc/devices、/proc/modules 文件系统支持、设备号与已加载模块 可用内容受内核配置影响
/proc/interrupts、/proc/softirqs 中断和软中断计数 配合 CPU 指标定位来源
/proc/kcore 内核内存的 ELF core 视图 显示很大不表示占用对应磁盘空间

控制台入口是 /proc/consoles,交换设备列表是 /proc/swaps。/proc/kcore 的显示大小不宜套固定公式;dmesg 的读取方式也不宜简化成“就是读取 /proc/kmsg”,现代实现可使用其他内核日志接口。proc 手册

进程目录常用入口

路径 用途 容易混淆的地方
cmdline 进程参数,通常以 NUL 分隔 不是内核 cmdline
environ exec 时的初始环境,以 NUL 分隔 空格没有被删掉,后续环境修改通常不在这里反映
cwd / exe 工作目录 / 可执行文件的符号链接 工作目录不必等于程序安装目录
root 进程看到的文件系统根 体现其挂载视图,与 JAVA_HOME 无关
fd / limits 打开的描述符 / 资源限制 读取其他用户的信息可能受权限限制
maps / smaps / smaps_rollup 映射范围 / 逐映射内存 / 汇总 字段和可用性随内核版本变化
status / stat / statm 可读状态 / 紧凑状态 / 内存统计 statm 的 resident 以页计,VmRSS 以 kB 标注
io / task I/O 计数 / 线程子目录 计数需做区间差,线程应核对 TID
1
2
3
4
5
6
7
8
tr '\0' ' ' < /proc/12466/cmdline
printf '\n'
readlink /proc/12466/cwd
readlink /proc/12466/exe
readlink /proc/12466/root
cat /proc/12466/limits
cat /proc/12466/io
ls /proc/12466/task

environ 使用相同的 NUL 分隔方式,按行查看可用 tr '\0' '\n' < /proc/12466/environ,但环境变量可能包含凭据,不应直接复制到公开日志。environ 手册、root 手册

按现象回查工具

现象 首选观察 下钻方向 判断边界
CPU 忙或 load 高 top、uptime、vmstat ps -L、线程采样、等待点 load 包含不可中断睡眠,CPU 口径受核数和配额影响
内存紧张 free、vmstat status、pmap、smaps available 看余量,si/so 看换页,PSS 减少共享页重复计数
磁盘满或 I/O 慢 df、df -i、iostat du、lsof +L1、iotop 容量、inode、设备延迟需分别观察
端口或连接异常 ss、lsof 进程身份、连接测试、应用日志 LISTEN 不代表业务请求成功
启动失败或行为不明 应用日志、ps、/proc 短时 strace 系统调用耗时不等于完整请求耗时

参考资料

字段解释以各节链接的一手手册为依据;阅读历史系统输出时,优先对照目标机器安装版本的 man。