先问个问题:你刚在一台 Linux 服务器上部署完服务,结果接口超时、页面转圈,这时候你第一反应是什么?十有八九是打开终端敲一句 ps aux,然后盯着一屏花花绿绿的输出发呆。进程列表是出来了,但脑子里还是一团浆糊——这一列列的到底谁是谁?为什么有的进程叫 <defunct>?为什么 kill -9 杀不掉它?为什么 CPU 都被一个叫 kswapd 的进程吃光了?
这篇文章不打算给你罗列五十个命令然后让你自己背,而是从「查看进程运行状态」这件事的核心痛点出发:看到输出之后,你到底该怎么读、怎么判断、怎么定位问题。我会从最常用的 ps 开始,把每一列讲透,再带你玩转动态监控 top / htop,最后专门处理几个高频疑难场景,比如 kill -9 杀不死、僵尸进程、进程状态卡在 D 等。无论你是刚入门的运维新手,还是写代码偶尔要上服务器排查的开发者,这篇文章里的招数应该都够用一段时间的。
1. 先学会用 ps:进程快照的正确打开方式
1.1 ps aux 和 ps -ef,两个流派到底看哪个
很多人第一次接触进程查看,基本都会被 ps aux 和 ps -ef 搞懵。这俩命令功能几乎一样,都是打印当前系统进程的快照,区别主要在输出格式和来源。
ps aux是 BSD 风格,输出里包含%CPU、%MEM、VSZ、RSS、STAT这些直观的资源占用和状态列,对排查性能问题特别友好。ps -ef是 System V 风格,它的输出更紧凑,包含PPID(父进程 PID),在梳理进程树、找父子关系时更好用。
我个人习惯把这两个都记牢,因为实际场景里它们各有不可替代的地方。比如你想快速看某个进程的 CPU 和内存占用,用 ps aux 一把梭;你想知道某个进程是谁拉起来的、父进程还在不在,用 ps -ef 更直观。
不过真正高频的使用姿势是组合查询:
bash复制# 查 nginx 相关进程
ps aux | grep nginx
# 查 java 相关进程,并保留 grep 本身那一行干扰也无所谓
ps -ef | grep java
这里有个小坑,grep 会把自己也匹配进去,所以输出里总有一条 grep --color=auto java 的进程。无伤大雅,但很多新手会被这条干扰,以为自己系统里跑了个奇怪的进程。更严谨一点可以这样:
bash复制ps aux | grep [j]ava
用方括号把首字母包起来,grep 就不会匹配到自身了。这个技巧适用于任何 ps | grep 的场景,算是老运维的基本操作。
1.2 每一列到底在说什么:从 PID 到 COMMAND 全拆解
ps aux 的输出长这样:
bash复制USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.4 168256 13184 ? Ss Jun10 0:53 /usr/lib/systemd/systemd --switched-root --system --deserialize 17
www 1234 0.3 2.1 4523000 652440 ? Ssl Jun10 12:34 /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
逐列解读一下:
- USER:这个进程是哪个用户启动的。注意,
root启动的进程不一定就是恶意的,但如果你在 web 服务目录下发现一个mysql用户跑的 bash,那就要警觉了。 - PID:进程唯一编号,后面所有操作都靠它。
- %CPU:进程占用 CPU 的百分比。这个值是 瞬时值,是多核 CPU 下的一个加权比例,100% 意味着吃满了一个逻辑核心。如果你看到某个进程一直维持在 100% 以上,说明它在多线程跑任务,不算异常。
- %MEM:进程占用物理内存的百分比,计算公式是 RSS 除以物理内存总量。
- VSZ:虚拟内存大小,单位 KB。它包含进程申请的但未必实际使用的地址空间,包括共享库、映射文件等。这个数字通常很大,不要看着慌。
- RSS:驻留内存大小,也就是进程实际占用物理内存的 KB 数。排查内存泄漏主要看它。
- TTY:进程关联的终端。
?表示这个进程没有关联终端,通常是后台守护进程,比如 systemd、nginx master 进程、数据库进程。 - STAT:进程状态,这个字段极其重要,后面的章节会专门展开。
- START:进程启动时间。
- TIME:进程累计消耗的 CPU 时间。注意,这个不是运行时长,而是占用 CPU 的总时长。一个进程跑了一天但 TIME 只有 0:01,说明它基本在睡觉。
- COMMAND:启动进程的命令行。如果命令特别长,
ps aux会截断显示,这时候可以用ps auxww看完整命令。
1.3 自定义输出列:ps 也支持精确制导
默认列虽然够用,但有些场景下你会希望只看自己关心的字段。比如你只想知道进程 PID 和完整启动命令,用来核对 jar 包版本:
bash复制ps -eo pid,user,cmd --sort=-rss | head -20
这个命令按内存占用从高到低排序,只看 PID、用户和完整命令行。-e 表示所有进程,-o 后面跟你想看的字段,--sort 指定排序字段。类似的字段名还包括 pcpu、pmem、stat、lstart(精确启动时间)、etime(已运行时长)等。
再比如你想看每个进程的运行时长,排查哪些进程是重启前的残留:
bash复制ps -eo pid,user,etime,cmd | sort -k3 -r | head
etime 的格式是 [[DD-]hh:]mm:ss,一眼能看出哪些进程已经跑了很久。这对判断「是不是昨天部署时漏杀的老进程」很有帮助。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. STAT 一列定生死:进程状态到底藏着多少信息
2.1 状态码逐字拆解:R、S、D、Z、T 都是什么含义
ps aux 里 STAT 列看起来只有一两个字符,但这其实是诊断进程问题的核心入口。我把常见状态码整理成一个速查表:
| 状态码 | 全称 | 含义 | 常见场景 |
|---|---|---|---|
| R | Running / Runnable | 进程正在运行,或位于运行队列中等待被调度 | 计算密集型任务、编译代码 |
| S | Sleeping(可中断) | 进程在等待某个事件,可以被信号唤醒 | 大多数服务进程的常态 |
| D | Uninterruptible Sleep | 不可中断睡眠,通常在等待 I/O 完成 | 磁盘读写压力大、NFS 挂载卡死 |
| Z | Zombie | 僵尸进程,子进程已结束但父进程未回收 | 父进程没调用 wait() |
| T | Stopped | 进程被暂停,通常是被 Ctrl+Z 或 SIGSTOP | 调试、后台任务暂停 |
| t | Tracing stop | 进程被调试器(如 gdb)暂停 | 断点调试中 |
| X | Dead | 进程已消亡(几乎不会看到) | 理论状态 |
| I | Idle | 内核线程的空闲状态 | 内核线程的睡眠态 |
在状态码后面还会跟一些附加标志字符,常见的有:
s:这个进程是会话首进程,一般来说就是一组进程的领头者,比如 nginx master 进程。l:多线程进程,Java 应用最常见。+:位于前台进程组,和你当前的终端有交互。比如你在终端里跑tail -f,它的状态就是S+。<:高优先级进程,可以抢占更多 CPU 时间。N:低优先级进程,也就是 nice 值为正的进程,比如后台备份任务。
看到这里你应该明白了,Ss 是会话首进程且正在睡眠,Ssl 表示它既是会话首进程又是多线程的,很多数据库和 Java 进程都是这种状态。R+ 则说明一个前台进程正在疯狂占用 CPU。
2.2 D 状态进程:为什么 kill -9 也拿它没办法
这是一个让无数运维头疼的场景:进程卡住了,kill -9 PID 下去,进程还在。敲 ps aux 一看,STAT 列明晃晃一个 D。
D 状态的全称是 Uninterruptible Sleep,中文叫不可中断睡眠。它意味着进程正在等待内核 I/O 操作完成,这段等待期间进程 不响应任何信号,包括 SIGKILL。最常见的触发原因是底层磁盘、网络文件系统(NFS)或 FUSE 文件系统卡住,比如:
- 磁盘硬件故障,导致读写请求一直发不出去。
- NFS 服务端无响应,客户端进程在等 RPC 请求超时。
- Ceph 或其他分布式存储挂载点异常。
这种情况下 kill -9 不会生效,因为内核根本没法把信号递交给一个睡死的进程。你能做的事情有限:
- 先排查 I/O 是不是真的卡死了。用
dmesg -T看内核日志,有没有task blocked、hung_task之类的报错;用iostat -x 1看磁盘的%util和await。 - 如果是 NFS 卡死,试试
umount -f强制卸载挂载点。卸载成功后,D 状态进程通常会迅速退出。 - 实在不行只能重启系统。这是最粗暴但有效的方案,前提是你能确认这个机器上没有更重要的东西。
要特别提醒的是,不要一看到 D 状态就想着 kill,因为如果问题出在磁盘或网络存储,即使杀掉进程,新的请求还是会继续堵在同样的 I/O 路径上。先把存储侧的问题解决才是正路。
2.3 僵尸进程:处理不及时会堆积成灾
僵尸进程的 STAT 是 Z,它其实是一个已经结束运行、但未被父进程回收的残留进程。进程退出后,内核会保留一个最小化的进程描述符,直到父进程调用 wait() 来读取子进程的退出状态。如果父进程一直不调用,子进程就会一直停留在僵尸状态。
产生僵尸进程的常见原因:
- 父进程代码逻辑有 bug,没有正确处理 SIGCHLD 信号或调用
wait()。 - 父进程本身也挂掉了,僵尸进程被 init/systemd 收养,但 systemd 可能不会立刻回收所有子进程。
单个僵尸进程不可怕,可怕的是数量级堆积。如果系统里出现成百上千个 Z 状态进程,会导致 PID 耗尽(默认 pid_max 通常是 32768 或更大),新进程无法创建。
排查和处理方式如下:
bash复制# 统计僵尸进程数量
ps -eo stat | grep -c '^Z'
# 找出僵尸进程的 PID 和父进程 PID
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'
确认僵尸进程的 PPID 之后,有两个处理方向:
- 如果父进程还活着且是个正常服务,可以重启这个服务。父进程重启后,它的子进程会被 systemd 收养,僵尸状态可能被清理。
- 如果父进程是 init/systemd(PPID 为 1),说明这个僵尸基本无法通过常规手段回收,只能重启系统。
这里的预防建议是:写守护进程或者多进程程序时,务必注册 SIGCHLD 信号处理函数,或者在事件循环里主动 waitpid(-1, &status, WNOHANG) 回收子进程。这是 C/C++/Python 多进程开发的基本功,别在这上面偷懒。
3. top / htop:从静态快照到动态战场
3.1 top 界面速读指南:那些数字真不是摆设
ps 是静态快照,适合一锤定音地查看某个时刻的状态。但如果问题是持续性的——比如 CPU 忽高忽低、内存缓慢上涨,静态输出就看不出趋势了,这时候需要动态监控工具。
top 是系统自带的神器,即便最小化安装的 Linux 发行版也会包含它。进入界面后,你看到的一大堆数字,每一块都有意义:
- 第一行:系统当前时间、已运行时长、登录用户数、负载均衡。重点看 load average 后面的三个数(1分钟、5分钟、15分钟平均负载),它们不是 CPU 使用率,而是处于运行或不可中断状态的进程数量。如果三个数持续高于 CPU 核心数,说明系统已经过载。
- 第二行:进程总数、运行中、睡眠中、停止、僵尸的数量。
- 第三行:CPU 使用率分布。
us是用户态占用,sy是内核态占用,ni是低优先级占用,id是空闲,wa是 I/O 等待,hi/si是硬件/软件中断。排查性能问题时,wa高说明 I/O 是瓶颈,us高说明计算密集。 - 第四、五行:物理内存和交换分区使用情况。注意
available这个字段,它才是系统真正可用的内存,别被free字段吓到,因为 Linux 会尽量把空闲内存用作缓存。
3.2 交互式快捷键:top 不只是看,还能操作
很多人用 top 只会按 q 退出,其实它的交互能力很丰富。进入 top 界面后:
P:按 CPU 使用率排序,默认就是这样。M:按内存占用排序。排查内存问题时第一件事就是按一下 M。T:按累计 CPU 时间排序。注意这跟P不一样,它排的是 TIME+ 列,可以看到哪些进程累计消耗 CPU 最多,适合找出「虽然现在不忙但长期吃 CPU 的慢性子」。k:杀掉指定 PID 的进程,top 会提示你输入 PID 和信号。比切出去开终端kill快。r:调整进程的 nice 值,也就是优先级。一般不建议手动乱调,除非你明确知道自己在做什么。1:查看每个 CPU 核心的使用情况。多核机器上某个核打满但其他核空闲,说明可能是单线程瓶颈。这个是排查 Java 单线程热点问题的常用手段。f:进入字段管理界面,可以增删显示哪些列,非常灵活。
在 top 界面里按 1 看每个核心的占用率,再按 P 看哪个进程占用最高,这是定位 CPU 飙高的标准三步走。
3.3 htop:更好看,也更好用
如果你觉得 top 的界面太复古,可以装一个 htop。它的优势在于:
- 彩色显示 CPU、内存、交换分区的使用条,一眼能看出压力分布。
- 操作方式更像图形界面,可以用上下键选进程,F9 直接杀进程,F6 选择排序字段,F5 显示进程树。
- 支持鼠标操作(部分终端下),对新手友好得多。
安装方法:
bash复制# Debian / Ubuntu
apt install htop
# RHEL / CentOS
yum install htop
htop 的进程树视图特别适合梳理服务依赖关系。比如你启动了一个 Java 应用,它内部 fork 了一堆子进程,你用 F5 能直观看到谁是谁拉起来的,这在排查进程残留、重启服务不彻底时非常实用。
有一个小技巧:在 htop 里按 u 可以选择只查看某个用户的进程,比如你怀疑 www 用户下有异常进程,直接过滤出来看,比在几十个进程里人工翻快得多。
4. 实战场景排查:从看到进程到解决问题
4.1 排查 CPU 飙高:找到那个占满内核的「真凶」
假设你收到告警,某台服务器的 CPU 使用率超过 90%。登录上机器后,按照下面的思路排查:
bash复制# 第一步:看负载和 CPU 分布
top
# 按 1 查看各核心占用,确认是否是单核打满
# 按 P 排序,找到 CPU 占用最高的进程 PID
接下来说一个高频场景:如果最高的是 Java 进程,怎样定位到具体线程?
bash复制# 找到 java 进程 PID,假设是 1234
top -Hp 1234
-H 表示以线程模式显示,-p 指定进程。你会在输出里看到一堆线程,其中某个线程 CPU 占用特别高。记下这个线程的 PID(假设是 5678),然后把它转换成十六进制:
bash复制printf '%x\n' 5678
得到类似 162e 的结果,这就是线程 ID 的十六进制形式。接着用 jstack 导出线程栈:
bash复制jstack 1234 | grep -A 30 '162e'
于是你能看到这个线程正在执行的代码栈,问题出在哪个类、哪个方法一目了然。这一整套组合拳是 Java 应用性能排查的基本功。
4.2 端口被占用:一条命令找到「幕后黑手」
部署新服务时最常见的错误就是端口冲突。报错信息通常是 Address already in use。这时要用的是 ss 命令:
bash复制# 查看 8080 端口被哪个进程占用
ss -lntp | grep 8080
输出类似:
bash复制LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("nginx",pid=1234,fd=9))
-l 只显示监听状态的连接,-n 显示数字端口而不是服务名,-t 只看 TCP,-p 显示进程信息。如果你是在非 root 用户下执行,-p 可能看不到 PID,需要用 sudo。
老牌的 netstat 命令也能做同样的事,但 ss 的性能和数据源都更好,我建议直接养成用 ss 的习惯。
4.3 进程的真实身份:顺着 /proc 挖到底
有时候光看命令行还不够。比如一个进程的 COMMAND 显示成 [kworker/0:1] 或者 [kswapd0],它到底在干什么?我们没法直接问它,但可以翻 /proc 目录。
/proc 是 Linux 内核暴露给用户态的一个虚拟文件系统,每个进程都有一个对应的 /proc/PID/ 目录。查看进程详情常用这几个文件:
bash复制# 进程的工作目录
ls -l /proc/PID/cwd
# 进程的启动命令(完整版,包含所有参数和换行)
cat /proc/PID/cmdline | tr '\0' ' '
# 进程打开的所有文件(包含 socket、管道、文件)
ls -l /proc/PID/fd | head
# 进程的环境变量
cat /proc/PID/environ | tr '\0' '\n' | head
其中 /proc/PID/fd 特别有用。如果你怀疑某个进程在写某个文件,但找不到文件路径,ls -l /proc/PID/fd 会显示文件描述符指向的实际路径。注意那里面最前面是 0(标准输入)、1(标准输出)、2(标准错误),后面才是真实文件。
还有一个小技巧:如果进程占用了磁盘空间但文件已删除,df -h 看到空间没释放,其实是有进程握着已删除文件。这时执行:
bash复制lsof | grep deleted
就能找出哪些进程持有已删除文件,重启对应进程即可释放磁盘空间。
4.4 深入了解 PID 相关的内核限制:进程数上限问题
你可能会碰到这种场景:系统还没怎么用,但创建新进程时报错 Cannot allocate memory 或者 Resource temporarily unavailable。排除真实内存不足之后,很可能是 PID 数量或者线程数达到了上限。
检查限制:
bash复制# 查看当前 PID 数量
cat /proc/sys/kernel/pid_max
# 查看当前已分配 PID 数量
ps -eLf | wc -l
# 查看当前用户的进程数限制
ulimit -u
临时调整方法:
bash复制# 修改当前 shell 的进程数上限
ulimit -u 65535
如果是系统级 pid_max 不够,直接写入内核参数:
bash复制echo 65535 > /proc/sys/kernel/pid_max
但这种修改重启后失效。要持久化,写入 /etc/sysctl.conf:
ini复制kernel.pid_max = 65535
再执行 sysctl -p 生效。
4.5 一个常见误区:kill -9 不是万能钥匙
最后必须聊一下 kill -9 的滥用问题。kill -9 发送的是 SIGKILL 信号,内核会直接强制终止进程,不给进程任何清理资源的机会。这会导致:
- 进程来不及关闭文件描述符,可能造成数据损坏。
- 来不及释放锁资源,可能导致其他进程死锁。
- 来不及写回缓冲区数据,数据库类应用可能丢数据。
- Java 应用可能中断正在进行的 JIT 编译,影响后续启动性能。
正确的做法是先用 kill -15(SIGTERM),这是「请优雅退出」的信号。大多数服务程序都会捕获 SIGTERM,在退出前完成清理工作。如果等了 30 秒还在,再考虑 SIGKILL。
有个场景例外:进程被 SIGTERM 信号卡住,比如它在清理资源时死锁了,或者某个第三方库忽略了所有信号。这时候也别慌,先试试 kill -3(SIGQUIT),对 Java 进程来说这会让 JVM 打印当前所有线程的线程栈到标准输出,是定位卡住原因的关键线索。
5. 最后分享几个实用小技巧
5.1 watch 命令:把静态命令变成动态监控
ps aux 只能看那一刻的快照,但你可以用 watch 让它定时自动刷新:
bash复制# 每 2 秒刷新一次进程列表,并按 CPU 排序
watch -n 2 'ps -eo pid,pcpu,pmem,comm --sort=-pcpu | head -20'
这在观察短生命周期进程(比如定时任务脚本)是否反复出现时特别好用。有些恶意脚本或计划任务会在执行后立刻退出,你用 ps aux 抓不到,但用 watch 一直盯着就能看到它一瞬间露头。
5.2 进程树:用 pstree 看清服务依赖
如果系统里服务多、进程关系乱,pstree 是梳理父子关系的最快方式:
bash复制pstree -p | grep -E 'nginx|java|mysql'
-p 会同时显示 PID,-a 可以显示命令行参数。某些情况下你会发现一个预期应该是「孤儿进程」的进程其实一直挂在某个守护进程下面,这种排查在和 systemd 打交道时尤其常见。
5.3 关于进程通信的简单提醒
既然你查到了某个进程不对劲,有时候还要知道它跟谁通信。虽然这不是查看运行状态的全部,但排查网络服务时,搞清楚进程监听的端口、建立的连接是必要的延伸。
bash复制# 查看进程 1234 的网络连接情况
ss -tnp | grep 'pid=1234'
看到它连了哪些外部 IP 和端口,基本就能判断是在正常提供业务服务,还是已经被恶意利用当作跳板。这一步在很多安全事件排查里都是关键环节。
看到这里,你应该已经掌握了从「运行 ps aux」到「读懂进程状态」「定位资源瓶颈」「处理疑难杂症」的完整链路。最后还想再强调一点:查看进程状态不是背命令,而是一个反复练习的观察-判断-行动过程。每一次诡异的进程状态背后,都对应着系统中某个具体的资源瓶颈或代码问题。下次再遇到服务器卡顿、进程杀不掉、端口被占用的情况,别急着重启机器,先静下心来把这个进程的状态弄明白,你会发现 Linux 的进程管理其实相当有迹可循。
