外观
进程与服务
排障第一步永远是 ps 和 top。这一节的重点是看懂 STAT 列和选对 kill 信号。
一、ps
bash
ps aux | head -10
ps -ef | grep java
ps -eo pid,ppid,user,stat,%cpu,%mem,etime,cmd --sort=-%cpu | head -10text
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 169312 12884 ? Ss Oct26 0:08 /usr/lib/systemd/systemd
app 28441 42.1 12.4 4214320 2012340 ? Sl 20:41 42:18 java -jar /app/app.jar
root 29411 0.0 0.0 11204 1024 ? S Oct26 0:02 /usr/bin/docker-proxy -proto tcp| 字段 | 含义 |
|---|---|
VSZ | 虚拟内存大小(含未实际分配的映射),通常很大,不用管 |
RSS | 实际驻留物理内存,看这个 |
STAT | 进程状态 + 附加标志 |
TIME | 累计 CPU 时间,不是运行时长 |
START | 启动时间 |
etime | 运行了多久(ps -eo etime) |
STAT 怎么看
第一个字母是主状态:
| 状态 | 含义 | 说明 |
|---|---|---|
R | Running / Runnable | 在跑或等 CPU |
S | Sleeping | 可中断睡眠,等事件(最常见) |
D | Uninterruptible Sleep | 等 IO,kill -9 也杀不掉 |
T | Stopped | 被 Ctrl+Z 或 SIGSTOP 暂停 |
Z | Zombie | 已退出但父进程没回收 |
I | Idle | 内核空闲线程 |
附加标志:
| 标志 | 含义 |
|---|---|
s | 会话首进程 |
l | 多线程 |
+ | 在前台进程组 |
< | 高优先级 |
N | 低优先级 |
D 状态和 Z 状态是两个大坑:
bash
ps -eo pid,stat,wchan:30,cmd | awk '$2 ~ /^D/'text
PID STAT WCHAN CMD
31882 D rpc_wait_bit_killable java -jar /app/app.jarwchan 是它卡在哪个内核函数。D 状态通常是磁盘/NFS 卡死,kill -9 无效,只能等 IO 超时或重启机器。
bash
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'text
PID PPID STAT CMD
42111 1 Z [java] <defunct>僵尸进程本身不占资源,但说明父进程没调 wait()。杀父进程才能回收。
二、top
bash
top -b -n 1 | head -20text
top - 21:32:11 up 1 day, 3:12, 2 users, load average: 2.41, 1.88, 1.62
Tasks: 214 total, 1 running, 213 sleeping, 0 stopped, 0 zombie
%Cpu(s): 38.2 us, 6.1 sy, 0.0 ni, 50.4 id, 5.1 wa, 0.0 hi, 0.2 si, 0.0 st
MiB Mem : 16006.2 total, 512.4 free, 13284.1 used, 2209.7 buff/cache
MiB Swap: 4096.0 total, 1204.2 used, 2891.8 free. 1150.2 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
28441 app 20 0 4214320 1.9g 182m S 42.1 12.4 42:18.11 java
29112 mysql 20 0 2143212 1.2g 96m S 8.2 7.4 12:41.02 mysqld关键行:
| 指标 | 含义 | 判断 |
|---|---|---|
load average | 1/5/15 分钟平均可运行 + 不可中断任务数 | 与核数比较,超过核数就是排队 |
us | 用户态 CPU | 应用在算 |
sy | 内核态 CPU | 系统调用多(网络/IO) |
wa | 等 IO | > 20% 说明磁盘是瓶颈 |
si / hi | 软中断 / 硬中断 | 网络包太密或驱动问题 |
st | 被虚拟化层偷走 | 云主机超卖 |
avail Mem | 可用内存(含可回收缓存) | 看这个,不是 free |
load average 的解读:
text
4 核机器,load 4.0 → 刚好跑满
4 核机器,load 12.0 → 平均有 8 个任务在排队
4 核机器,load 4.0 但 CPU 空闲 60% → 有 D 状态进程卡在 IO最后一种最容易被误判:"负载高但 CPU 不高" = IO 或锁等待。
三、找进程
bash
pgrep -af java # -a 显示完整命令行
pgrep -u app -f 'app.jar'
pidof nginx
pstree -p 31882 | headtext
28441 java -jar /app/app.jartext
java(28441)─┬─{java}(28442)
├─{java}(28443)
└─{java}(28444)pgrep -f 匹配完整命令行,pgrep java 只匹配进程名(会被截断到 15 字符)。
四、kill 信号
bash
kill -15 28441 # SIGTERM,默认,请求优雅退出
kill -9 28441 # SIGKILL,强杀,进程无法捕获
kill -HUP 28441 # SIGHUP,很多服务用它来 reload 配置
kill -0 28441 # 不发信号,只检查进程是否存在($? 为 0 表示存在)| 信号 | 编号 | 用途 |
|---|---|---|
SIGHUP | 1 | 重载配置 / 终端断开 |
SIGINT | 2 | Ctrl+C |
SIGQUIT | 3 | 退出并 dump(Java 的线程栈) |
SIGKILL | 9 | 强杀,不可捕获、不可清理 |
SIGTERM | 15 | 默认,优雅退出 |
SIGUSR1 | 10 | 应用自定义(如日志切割) |
SIGSTOP / SIGCONT | 19/18 | 暂停 / 继续 |
kill -9 是最后手段
SIGKILL 不给进程任何清理机会:临时文件不删、缓冲区不 flush、数据库连接不断开、分布式锁不释放。Java 应用被 -9 后,未提交的事务、未落盘的日志全丢。
正确顺序:SIGTERM → 等 10~30 秒 → 还活着再 SIGKILL。
bash
kill -15 28441 && sleep 15 && kill -0 28441 2>/dev/null && kill -9 28441五、优先级
bash
nice -n 10 /opt/backup.sh # 以低优先级启动
renice -n 5 -p 28441 # 调整已运行进程
ionice -c 3 -p 28441 # IO 优先级:3 = idle,最闲时才跑nice 范围 -20(最高)到 19(最低),普通用户只能调高(降低优先级)。
备份、日志压缩这类任务用 nice -n 19 ionice -c 3,避免和业务抢资源。
六、后台运行
bash
# 临时后台
./start.sh &
# 退出终端后继续跑(nohup + 重定向)
nohup ./start.sh > /var/log/start.log 2>&1 &
# 已经在前台跑了,想放到后台:Ctrl+Z 然后
bg
disown -h %1
# 完全脱离终端
setsid ./start.sh </dev/null >/var/log/start.log 2>&1 && 不等于守护进程
直接 ./start.sh & 在关闭 SSH 时仍会收到 SIGHUP 被杀。nohup 才是屏蔽 SIGHUP 的正确方式,且必须重定向输出,否则退出后写终端会报错。
长期服务不要用这套,用 systemd(见 systemd 单元)。
七、/proc 里的信息
bash
PID=28441
cat /proc/$PID/cmdline | tr '\0' ' '; echo
ls -l /proc/$PID/cwd
tr '\0' '\n' < /proc/$PID/environ | grep -i java_opts
ls -l /proc/$PID/fd | head
cat /proc/$PID/limits | grep -i 'open files'text
java -jar /app/app.jar
lrwxrwxrwx 1 app app 0 Oct 27 21:30 /proc/28441/cwd -> /app
JAVA_OPTS=-Xms1g -Xmx1g -XX:+UseG1GC
lrwx------ 1 app app 64 Oct 27 21:30 0 -> /dev/null
lrwx------ 1 app app 64 Oct 27 21:30 1 -> /data/app/logs/app.log
lrwx------ 1 app app 64 Oct 27 21:30 2 -> /data/app/logs/error.log
Max open files 65535 65535 filescmdline 用 \0 分隔参数,直接 cat 会粘在一起。fd 能看到它打开了哪些文件——排查"删了文件空间不释放"就靠这个。
八、ulimit
bash
ulimit -n # 文件句柄
ulimit -u # 最大进程数
ulimit -atext
open files (-n) 1024
max user processes (-u) 317501024 对服务端远远不够。永久修改:
ini
# /etc/security/limits.conf
app soft nofile 65535
app hard nofile 65535systemd 服务要在 unit 里设(limits.conf 对 systemd 服务不生效):
ini
[Service]
LimitNOFILE=65535九、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
kill -9 杀不掉 | 进程在 D 状态 | 等 IO 超时,或重启机器 |
| 一堆僵尸进程 | 父进程不回收 | 重启父进程 |
| 负载高但 CPU 空闲 | D 状态进程卡 IO | ps -eo stat,wchan 定位 |
ps 里 RSS 很大但内存没降 | 共享库/页缓存计入 | 看 PSS(smem)更准 |
| 服务重启后进程还在 | 用了 nohup & 留下的野进程 | pkill -f,改 systemd |
nohup 后日志为空 | 只重定向了 stdout 没管 stderr | > log 2>&1 |
ulimit -n 改了不生效 | 对 systemd 服务无效 | 在 unit 里设 LimitNOFILE |
| 进程名匹配不到 | pgrep 按名字截断到 15 字符 | 用 pgrep -f 匹配命令行 |