半夜被监控电话叫醒的次数多了以后,你会形成一种肌肉记忆:登录服务器,先敲 free -h 看内存是否还有余量,再敲 top 看谁在吃 CPU,需要翻细节时马上用 ps 补一刀。Linux 命令进程实时监控(top)、进程静态查看(ps)、内存使用查看(free),这三个命令听起来基础,但真遇到线上故障时,能用得又快又准的人和只会看前几行的人,差距非常大。
这篇文章不打算照抄 man 手册,我把自己多年实际运维和开发中常用的参数、输出字段的坑、以及组合排查的思路全部整理出来。不管你是刚接触 Linux 的运维新手,还是日常要在服务器上排查问题的后端开发者,都能直接照着用。面试前拿来突击这块知识点也足够用。
1. 先用 top 抓住现场:实时监控进程的第一反应
1.1 top 界面五行头部信息,逐行拆开看
很多人敲完 top 之后只盯着第一屏的进程列表看,头部那五行信息反而没认真读过。实际排查的时候,头部信息往往比进程列表更能说明问题。
第一行是系统整体概览:当前时间、系统已运行时长、登录用户数,以及最关键的 load average 三个数字。这三个数字分别对应过去 1 分钟、5 分钟、15 分钟的平均负载,注意它代表的不是 CPU 使用率,而是处于可运行状态和不可中断状态的进程平均数。怎么判断负载高不高?要看 CPU 核数:4 核服务器 load average 长时间超过 4.0,说明已经有进程在排队了;如果长期超过 8.0,基本可以确定系统在满负荷甚至过载运行。我习惯按核数乘以 0.7 作为警戒线,比如 8 核机器 load 超过 5.6 就开始查。
第二行 Tasks 统计进程总数以及 running、sleeping、stopped、zombie 四种状态的数量。zombie 那一列平时都是 0,一旦长期不为 0,说明系统里有僵尸进程没被父进程回收,这个后面在 ps 部分会展开讲。
第三行 %Cpu(s) 是最容易被忽略也最需要理解的一行。us 是用户态 CPU 占用,sy 是内核态占用,id 是空闲,wa 是等待 IO 完成。如果 us 高说明业务计算量大,sy 高说明系统调用频繁或内核态有瓶颈,wa 高则要立刻怀疑磁盘 IO 或网络文件系统。云服务器上还常看到 st,这个是虚拟化环境里被宿主机偷走的时间,st 高说明宿主机资源竞争严重。
第四行和第五行的 Mem、Swap 就是内存情况,跟 free 命令展示的内容同源,后面第三章统一讲。你只需要知道 top 里的 Mem 和 Swap 单位是 KiB,一旦 Mem 的 free 很小而 swap 开始被使用,就要留意内存压力了。
1.2 实战最常用的几个 top 交互快捷键
top 是交互式命令,进去之后光看默认输出是不够的,几个快捷键必须形成条件反射。
按 P 按 CPU 使用率排序,按 M 按内存使用率排序,这两个是最高频的操作。系统一卡,我通常是先按 P 看谁在烧 CPU,再按 M 看谁在吃内存。按 T 是按累计 CPU 时间排序,适合找那种长期消耗 CPU 但瞬时占比不高的进程。按 1 可以展开每个 CPU 核的单独使用率,多核服务器上某个核跑到 100% 而其他核空闲,这种不均衡现象靠默认的合并显示根本看不到。
k 是杀掉进程,按下后它会让你输入 PID,然后再让你输入信号,默认是 15(SIGTERM),如果进程不响应再输入 9(SIGKILL)强制杀。r 可以调整进程的 nice 值,也就是优先级。u 后面接用户名可以只显示某个用户的进程,多用户服务器上排查时特别有用。H 键切换线程模式,能看到进程内部的线程,Java 应用排查线程问题时必须用到。d 或 s 可以修改刷新间隔,默认是 3 秒,排查时我经常改成 1 秒抓得更紧。
这些快捷键记不住没关系,top 界面里按 h 也有提示,但 P、M、k、1 这四个建议死死记住,它们是排查故障最常用的。
1.3 top 的批处理模式:脚本抓快照的正确姿势
交互模式下 top 必须一直开着,但很多场景我们需要在脚本里抓取一次快照,比如定时采集、告警触发时自动记录现场。这时候就要用批处理模式。
top -d 2 是每 2 秒刷新一次,适合人工盯屏。top -p 1234 -p 5678 可以只监控指定的多个 PID,进程多了以后单独盯几个目标非常有用。top -u www 是按用户过滤,比如只观察 www 用户跑的所有进程。top -bn1 是批处理模式输出一次后自动退出,这是写脚本最常用的组合。
我踩过的坑是 top -bn1 只采样一次时,输出的排序和数值在负载波动时会有点失真。内核统计 CPU 时间本身有延迟,单次采样可能抓到的是上一个周期残留下来的值。更稳的写法是用 top -bn2 然后取第二次的输出,或者 shell 里循环跑几次再平均,脚本示例如下:
bash复制#!/bin/bash
for i in 1 2 3; do
top -bn1 | head -20
sleep 1
done
批处理模式的输出与交互模式类似,但会直接打印到标准输出,用管道接 awk、grep 做二次处理非常方便。比如要提取所有 CPU 占用超过 50% 的进程,可以用 top -bn1 输出后配合 awk 判断第 9 列。
1.4 一次 CPU 飙高现场回放:top 是怎么帮我定位的
有一次线上 Java 服务突然响应缓慢,我登上服务器后第一时间敲 top,然后按 P。屏幕上立刻可以看到一个 java 进程的 %CPU 显示为 380%,注意这个值超过了 100%,说明它占满了差不多 4 个 CPU 核。这个细节很重要,单核 CPU 使用率上限是 100%,多核服务器上一个进程最多可以占到 CPU 核数乘以 100%,看到超过 100% 不要慌,它只是多线程跑满了多个核。
接着我记下这个 java 进程的 PID,按 H 进入线程模式,又按 P 排序,找到具体消耗 CPU 的线程 TID。再把这个十进制 TID 转成十六进制,用 jstack 去抓线程栈,最后定位到是某个 Redis 连接池的热点方法出了问题。整个过程 top 用了不到两分钟就锁定了方向,这就是实时监控的价值——第一时间知道问题出在哪个进程,后面所有操作才有目标。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ps 静态快照:不是简单的 grep 加 kill
2.1 有了 top 为什么还要用 ps
top 是持续刷新的动态视图,适合“抓住现场”;ps 是某一时刻的静态快照,适合“精确翻查”。两者的定位完全不同。
故障发生时我习惯先用 top 看全局,盯住可疑的目标,然后马上用 ps 做精确过滤,比如查看这个进程的完整启动命令、父进程是谁、运行了多久、RSS 内存是多少。另外 top 的输出会不断变化,没法直接扔给脚本做稳定的逻辑处理;ps 输出一次成型,格式固定,管道处理起来干净利落。ps 还适合做历史回溯,比如排查完问题之后,想确认当时那个进程是不是通过某个特定参数启动的,ps 翻一眼 PID 对应的完整命令行就知道了。
2.2 ps 的三种写法:-ef、aux 与 GNU 风格区别
ps 命令最劝退新手的是它同时支持三种风格:Unix System V 风格、BSD 风格、GNU 风格。它们长得像,但细节差异很大。
ps -ef 是 System V 风格,-e 表示显示所有进程,-f 表示完整格式。输出列包括 UID、PID、PPID、C、STIME、TTY、TIME、CMD。注意这里的 C 是 CPU 使用率的估算值,TIME 是进程累计消耗的 CPU 时间,而不是进程运行时长。
ps aux 是 BSD 风格,a 显示所有用户的有终端进程,x 也包含没有控制终端的进程,u 表示以用户格式显示。输出列比 -ef 多了 %CPU、%MEM、VSZ、RSS、STAT、START。由于它不带横杠,很多新手会写错成 ps -aux,在部分系统上 ps aux 和 ps aux 行为基本一致,但严格来说 -aux 在 POSIX 标准里是另一层含义,建议直接养成写 ps aux 的习惯。
第三种是 GNU 风格的长选项,比如 ps aux --forest、ps -eo pid,ppid,%cpu,cmd --sort=-%cpu。这种自定义输出方式最灵活,也是我写脚本时最常用的。三种风格的列名不完全一样,判断进程状态时注意先确认当前用的是哪种输出格式。
2.3 ps 输出字段里最容易被误读的几个
ps aux 输出中 VSZ 和 RSS 是最容易被误读的两个字段。VSZ 是虚拟内存大小,它包含进程已经映射但可能从未真正使用过的地址空间;RSS 是常驻物理内存,是进程实际占用的物理内存页总和。判断进程真实内存占用看 RSS,不要看 VSZ。我见过有人拿 VSZ 去评估 Java 应用内存占用,一个 JVM 的 VSZ 可能是几十 GB,但很多只是保留地址空间,物理内存其实没占那么多。当然多进程共享的共享库在 RSS 里会被重复计算,所以几个进程 RSS 加起来可能超过实际物理内存,这也是正常的。
STAT 状态列也值得认真看。R 是运行中,S 是可中断睡眠,D 是不可中断睡眠。D 状态是最让人头疼的,它通常是进程正在等待磁盘 IO 或网络 IO 完成,这种状态下 kill -9 都杀不掉,只能等 IO 超时或者恢复。排查时如果发现大量进程处于 D 状态,基本可以断定磁盘或存储层面出了问题。Z 是僵尸进程,进程已经死掉但父进程没调用 wait 回收,zombie 本身不吃 CPU 也不吃内存,但大量积压说明父进程可能有 bug。
TIME 列也容易被误读。它表示进程累计消耗的 CPU 时间,格式是 分钟:秒,比如某进程 TIME 是 10:30,意思是它累计占用了 10 分 30 秒的 CPU 时间,并不是说它运行了 10 分 30 秒。STAT 状态里的附加字符如 s 表示会话首进程、+ 表示在前台进程组、< 表示高优先级,这些对排查交互进程和后台进程的关系也很有帮助。
2.4 找特定进程和去 grep 自身的几个技巧
最基础的查找方式是 ps -ef | grep java,但这条命令有个经典问题:grep 进程本身也会被匹配出来,输出里混入一条 grep 命令。规避方式是用字符类技巧:grep [j]ava,这样 grep 进程的命令行里是 [j]ava 而不是 java,正则匹配时反而不会命中自身。
更干净的做法是直接用 pgrep 或 pgrep -f。pgrep -f java 会返回匹配的 PID 列表,加 -l 会同时显示进程名,加 -a 显示完整命令行。在脚本里拿到 PID 之后可以交给 kill、top -p 或者读取 /proc/PID 继续处理。
ps -C java 是按进程名精确匹配,注意它是精确匹配进程名而不是命令行内容。ps -u www 按用户过滤。需要自定义输出列的时候用 ps -p PID -o pid,ppid,%cpu,%mem,rss,cmd --sort=-%cpu 这种方式,列名可以自由组合,脚本里解析起来非常稳定。
2.5 ps --forest 看进程树:处理僵尸进程的完整思路
ps -ef --forest 会以树状结构展示进程之间的父子关系,这个视图在排查僵尸进程时几乎是必需的。
僵尸进程本身无法通过 kill 清除,因为它已经死了,只是内核还没有回收它的进程描述符。要处理僵尸进程,关键是找到它的父进程:用 ps -ef 里僵尸进程的 PPID,再用 ps --forest 看父进程为什么不回收子进程。正常父子关系下,父进程结束或调用 wait 后子进程会被 init 进程接管并回收;父进程如果是常驻服务且有 bug,一直不调用 wait,僵尸就会一直堆积。处理方式一般是先确认父进程能否重启,如果不能重启,只能考虑让父进程退出,让 PID 1 接管回收。
有一次我排查一个 PHP-FPM 服务器的僵尸进程堆积问题,用 ps -ef --forest 后发现所有僵尸进程的父进程都是同一个 master 进程,重启 PHP-FPM 后僵尸立刻被全部回收,前后不到一分钟。这个思路比盲目 kill -9 有效得多。
3. free 看内存:别被 used 和 free 两个数字骗了
3.1 free 全字段拆解与常用单位参数
free 默认输出单位是 KiB,直接看会不太直观,实际使用中几乎都会带单位参数。free -m 以 MiB 为单位,free -g 以 GiB 为单位,free -h 会自动选择人类易读的单位,比如 1.5G、3.2M。需要连续观察趋势时加 -s 参数,free -s 3 会每 3 秒刷新一次,这个在压测场景里很有用。
输出表里 total 是物理内存总量,used 是被使用内存,free 是完全没有被使用的内存,shared 是 tmpfs 等共享内存使用的量。buff/cache 是内核用于块设备缓冲和页面缓存的量,available 是预估的、可供新进程使用的内存量。绝大多数新手栽在 used 和 free 上,就是因为没想清楚 buff/cache 的角色。
3.2 available 才是真正能用的内存:理解 buff/cache
很多人看到 free 输出中 free 只剩几十 MB,used 占满 90% 以上,就开始紧张,其实这不一定有问题。Linux 内存策略和 Windows 不太一样,它倾向于把空闲内存拿来当缓存用,也就是 buff/cache。buff/cache 占据的内存并没有“丢”,当有新的程序需要内存时,内核会快速回收这部分缓存分配出去。
我常用的类比是:buff/cache 就像一个公司茶水间囤的咖啡豆,看起来库存占了很大空间,但有人需要喝的时候随时可以拆开使用。所以判断内存够不够,不是看 free 那一列,而是看 available 那一列。available 是内核综合判断当前可回收缓存和其他因素后给出的真实可用内存估计值,在 3.14 以上版本的内核中 free 才会显示这一列。可以用 available 占总内存的百分比来辅助判断,低于 10% 且持续走低才需要警惕;有些老版本没有 available 列,只能用 free + buff/cache 近似估算。
那 used 列到底算什么?它等于 total 减去 free 再减去 buff/cache,所以它展示的是“剔除缓存后实际被业务和内核自身占用的内存”,这是一个偏保守的指标。把 used 当作内存告警依据经常误报,尤其是文件读写频繁的机器上 buff/cache 明显偏高,这属正常现象。
3.3 判断内存吃紧的三个信号
真正需要紧张的是以下三个信号。第一,available 占比持续走低,比如已经低于总内存的 10%;第二,swap 的使用量持续增长,说明内核已经把部分内存页换到磁盘上,内存确实不够用了;第三,用 vmstat 观察 si 和 so 两列,也就是 swap in 和 swap out 的速率,这两个值经常大于 0,说明系统正在频繁进行内存和磁盘之间的换入换出,性能损耗非常大。
判断时不要只看一次 free 输出,要用 free -s 3 连续观察,或者和 vmstat、dmesg 配合。如果服务本身处于启动阶段或刚进行过大文件操作,内存数据短暂波动是正常的,持续观察几分钟才有意义。
3.4 补充 /proc/meminfo 与单进程内存查看
free 的数据源其实是 /proc/meminfo,想看得更细可以直接读这个文件。里面除了 MemTotal、MemFree、MemAvailable 之外,还有 Buffers、Cached、SwapCached、Committed_AS、CommitLimit 等字段。Committed_AS 表示系统当前已经承诺分配给进程的内存总量,CommitLimit 是内核允许的承诺上限,当 Committed_AS 超过 CommitLimit 时可能触发 overcommit 问题,极端情况下 OOM killer 会开始杀进程。
单进程的内存细节就要看 /proc/PID/status 了,里面 VmSize、VmRSS、VmPeak 分别表示虚拟内存大小、常驻内存大小、历史峰值内存。定位某个进程内存异常增长时,cat /proc/PID/status 比 ps 单独一眼更直观,尤其是 VmPeak 能告诉你它历史上最多吃到多少内存。
3.5 一个 OOM 案例:free 说内存不够时到底怎么查
有一次我负责的应用频繁被系统杀掉,dmesg 日志里能看到 Out of memory 字样。我登上服务器先跑 free -h,发现 available 只有几百 MB,而 swap 几乎用满。再跑 ps aux --sort=-%mem | head,看到某个 Java 服务的 RSS 已经超过了机器物理内存的一半,加上其他常规进程,内存确实被耗尽。
这时候不光要确认谁吃了内存,还要搞清为什么没有及时释放。Java 进程的堆外内存、连接池、线程栈都可能成为隐形内存大户。我看了下 /proc/PID/status 里的 VmRSS 和 VmPeak,确认它已经涨到历史最高值,最终通过调整堆内存参数和连接池大小解决了问题。整个过程中 free 负责判断系统整体的内存水位,ps 负责锁定具体进程,/proc/PID/status 负责确认细节。
关于网上偶尔看到的 echo 3 > /proc/sys/vm/drop_caches,它能把 pagecache 和可回收内存释放掉,让 free 的时候空闲内存看起来变多,但我不建议在生产环境盲目执行。缓存本来就是内核用来加速文件读取的,强制清掉会让热数据重新走磁盘,反而拖慢性能。除非是测试环境复现问题,否则别动这个参数。
4. 三剑客联动:一次服务卡顿的完整排查流程
4.1 标准排查套路:从告警到定位的五步走
单看某个命令只能看到一面,组合起来才能讲清楚完整的问题。我的标准排查流程基本是五步。
第一步,free -h 看内存水位和 swap 是否增长,先排除内存耗尽这个最常见的诱因。第二步,top 按 P 看 CPU 占用排序,按 M 看内存占用排序,先建立全局印象。第三步,ps aux --sort=-%cpu | head 和 ps aux --sort=-%mem | head 分别拿出 CPU 和内存占用前几名的精确列表,记录 PID。第四步,针对可疑 PID 进一步深入,用 ps -fp PID 看启动命令和父子关系,用 top -Hp PID 看线程级别,或者 cat /proc/PID/status 看内存细节。第五步,结合 dmesg、应用日志、监控系统,确认是 OOM、代码死循环、还是磁盘 IO 阻塞。
这套流程不管故障表象是响应慢还是进程消失,都能快速收敛到具体目标。它不依赖任何 fancy 工具,纯靠 Linux 自带命令就能跑完,这也是我优先推荐的原因。
4.2 常见故障场景与命令组合速查表
| 故障现象 | 推荐命令组合 | 重点关注 |
|---|---|---|
| CPU 使用率飙升 | top 按 P,ps aux --sort=-%cpu | 进程 %CPU 是否超过 100%,是否多核全满 |
| 内存不足 / OOM | free -h,ps aux --sort=-%mem,cat /proc/PID/status | available 占比、RSS 大户、VmPeak 历史峰值 |
| load average 很高但 CPU 空闲 | top 看 wa,ps -ef 看 D 状态进程 | 等待 IO 是否过多,存储层是否异常 |
| 系统响应慢但 CPU/内存都正常 | vmstat,free -s 3 | swap 是否频繁换入换出,si/so 是否持续非零 |
| 僵尸进程堆积 | ps -ef --forest | 找 PPID,处理父进程,必要时重启服务 |
| 某个进程持续异常重启 | ps -ef -o pid,ppid,cmd | 父进程是谁,是否被守护进程拉起 |
这个表是浓缩后的经验,实际排查时每个场景几乎都还要组合第四步和第五步的深入检查,但方向对了就不会浪费太多时间。
4.3 三命令与 /proc 文件系统的深层联系
讲完了命令本身,再补充一层底层逻辑。top、ps、free 看起来是三个不同的工具,实际上它们都在读 /proc 文件系统。top 和 ps 读的都是 /proc/PID/stat、/proc/PID/status、/proc/stat 这类文件,free 读的是 /proc/meminfo。理解了这一点,你再去研究那些“冷门字段”时思路会打开很多。
比如手动查看系统负载可以直接 cat /proc/loadavg,查看实时内存可以 grep MemAvailable /proc/meminfo,查看进程状态可以直接 cat /proc/PID/status。这些原始数据在某些精简系统上没有命令可用时可以作为兜底方案。另外理解了数据来源,也能解释为什么 top 第一次采样不准、为什么 free 的 available 需要内核单独计算。
4.4 把三个命令组合成一条监控脚本
日常巡检或者告警触发时,把三个命令串成一个脚本非常方便。下面是我常用的一个简化版,直接执行就能打印当前系统最关键的几块信息。
bash复制#!/bin/bash
echo "===== 系统负载 ====="
uptime
echo "===== 内存概览 ====="
free -h
echo "===== CPU 占用 Top 5 ====="
ps aux --sort=-%cpu | head -6
echo "===== 内存占用 Top 5 ====="
ps aux --sort=-%mem | head -6
echo "===== 当前可疑进程状态 ====="
ps -eo pid,ppid,stat,comm | grep -E " Z| D" | grep -v grep
脚本输出后可以直接贴在排查记录里,也可以挂到 cron 上定期执行。第五行那段 grep 是专门看僵尸和不可中断睡眠进程的,不需要每次都在千行 ps 输出里人工找。脚本跑完大概两秒钟,但系统当时的整体状态已经被记录下来了。
排查问题这件事,方法永远比工具本身更重要。掌握了 top、ps、free 各自的长处和短板,再配合一套固定的决策流程,你在服务器上遇到任何异常都不会再慌了。我自己刚入行时也盯着 free 的 used 数值紧张过半天,后来踩过几次 OOM 和僵尸进程的坑,才意识到这些命令最值钱的地方不是参数本身,而是输出里每个字段背后的系统运行状态。建议你有空时在测试机器上把这几个命令的每一列都对着 /proc 目录看一遍,一个月之后你会对 Linux 的内存和进程调度有完全不一样的感觉。
