CPU 飙高排查清单
1、快速定位高 CPU 进程
top -c # 按 P 按 CPU 排序,记下 PID
# 或
htop # 更直观,F6 选 PERCENT_CPU
# 或
pidstat -u 1 # 每秒刷新,看 %CPU 列
2、定位进程内高消耗线程
top -H -p <PID> # 看该进程所有线程的 CPU 占用
# 或
pidstat -u -t -p <PID> 1 # 线程级 CPU 明细
3、线程 TID 转 16 进制(用于查堆栈)
printf "%x\n" <TID> # 输出如 0x1a2b
4、抓线程/进程在干什么(找热点代码)
# Java 进程
jstack <PID> > /tmp/js.txt
# 然后 grep 0x1a2b /tmp/js.txt 找到对应线程栈
# 非 Java / 通用
perf top -p <PID> # 实时看热点函数
# 或采样生成火焰图
perf record -p <PID> -g -- sleep 30
perf script > /tmp/out.perf
# 用 FlameGraph 工具生成 svg
5、看系统级上下文切换是否异常
vmstat 1 10 # 看 cs 列(context switch)
pidstat -w 1 # 每个进程的 cswch/s(自愿切换)和 nvcswch/s(非自愿)
6、看软中断是否吃 CPU
top # 看 si 占比
cat /proc/softirqs # 各 CPU 软中断分布
mpstat -P ALL 1 # 看每个 CPU 的软中断占比
内存问题排查清单
1、先看整体内存状况
free -h # 重点看 available 列
vmstat 1 # 看 si/so 是否有换页
cat /proc/meminfo # 详细内存分布
2、找内存占用最高的进程
ps aux --sort=-%mem | head -20
top -c # 按 M 按内存排序
# RES = 实际物理内存占用
# VIRT = 虚拟内存(通常不用太关注)
3、分析某个进程的内存明细
pmap -x <PID> | head -30 # 内存映射,看哪块占大头
pmap -x <PID> | tail -1 # 最后一行是汇总
cat /proc/<PID>/status # 看 VmRSS / VmSize
ps -p pid -o pid,ppid,stat,%cpu,%mem,rss,vsz,nlwp,etime,cmd #看进程整体状态
#%CPU:进程正在消耗多少 CPU。多线程进程可能超过 100%
#%MEM、RSS:当前实际占用的物理内存;排查内存增长时重点看 RSS
#NLWP:线程数量
#STAT:进程状态,例如 R 运行、S 可中断睡眠、D 不可中断等待、Z 僵尸
#ETIME:进程已运行多久
4、判断是缓存还是真用掉了
# buff/cache 高但 available 充足 → 正常缓存,无需处理
# 如果怀疑缓存不释放,可手动释放(测试环境)
sync; echo 3 > /proc/sys/vm/drop_caches
5、检测内存泄漏趋势
watch -n 1 "grep VmRSS /proc/<PID>/status"
# 或
pidstat -r -p <PID> 1 # 看 minflt/majflt 和 RSS 趋势
I/O 与负载高排查清单
1、负载高但 CPU 不高 → 查 I/O
top # 看 wa(I/O wait)列
vmstat 1 # 看 b(不可中断进程数)、wa、si/so
iostat -xz 1 # 看 %util、await、r/s、w/s
2、找哪个进程在疯狂 I/O
iotop -o # 只看有 I/O 的进程
pidstat -d 1 # 每个进程的读写速率
3、查 D 状态(不可中断睡眠)进程
ps aux | awk '$8 ~ /D/ {print}'
# 或
top -b -n 1 | awk '$8 ~ /D/ {print}'
4、查文件/磁盘层面
df -h # 看磁盘是否满了
lsof +D /path/to/dir # 看目录下被打开的文件
lsof -u <user> # 看某用户打开的文件
# 磁盘满但 df 显示有空间?查被删除但未释放的文件句柄:
lsof | grep deleted
OOM / 进程被杀死排查清单
1、确认是不是 OOM Killer 干的
dmesg -T | grep -i "oom\|killed process"
# 或
journalctl -k | grep -i oom
# 或
grep -i "oom" /var/log/messages
grep -i "killed process" /var/log/syslog
2、看 OOM 时系统内存状态
# dmesg 输出里会包含:
# - 被杀进程 PID、名称、RSS 等
# - 当时 Mem-Info(free/active/inactive/slab 等)
# - oom_score 和 oom_score_adj
3、看进程 OOM 评分
cat /proc/<PID>/oom_score # 越高越容易被杀
cat /proc/<PID>/oom_score_adj # 可调负分保护关键进程
4、临时保护关键进程
echo -1000 > /proc/<PID>/oom_score_adj
# 或永久设置
sysctl vm.panic_on_oom=0 # 不 panic,继续杀进程
5、如果应用频繁 OOM
用 jmap(Java)或 valgrind(C/C++)分析内存使用
加监控告警(内存 > 80% 时提前介入)
调 JVM 堆大小 / 应用内存参数
总结:
| 场景 | 首选命令 | 关键指标 |
|---|---|---|
| CPU 高 | top -H -p PID | %CPU、负载 |
| 上下文切换 | vmstat 1 | cs 列 |
| 内存不足 | free -h | available |
| 内存泄漏 | pidstat -r | RSS 趋势 |
| I/O 瓶颈 | iostat -xz 1 | %util、await |
| D 状态 | ps aux \| awk '$8~/D/' | D 状态进程 |
| OOM | dmesg \| grep -i oom | killed process |
| 端口/文件占用 | lsof -i :8080 | 打开的文件/套接字 |
说明
1、负载高但 CPU 不高 是什么意思?
负载高但 CPU 不高 = 很多进程在排队,但不是在等 CPU,而是在等 I/O(磁盘 / 网络 / NFS / 锁),简单理解就是:CPU 很闲,但活干不下去,因为“原材料”或“出口”堵了
在 Linux 里,负载 = 处于以下三种状态的进程数之和:
| 状态 | 含义 |
|---|---|
| R(Running) | 正在用 CPU |
| R(Runnable) | 等 CPU(CPU 排队) |
| D(Uninterruptible Sleep)重点!!!! | 等 I/O(磁盘 / NFS / 网络存储) |
- CPU 100% → 负载一定高 ✅
- 负载高 → CPU 不一定高 ❌(可能全是 D 状态)
场景 1:磁盘 I/O 被打满(最常见)
- 大量日志写入
- 数据库疯狂刷盘
- 备份程序在拷大文件
- 云服务器 IOPS 被打爆
此时,进程发起 read() / write(),内核进入 D 状态,CPU 其实在“等磁盘”,进程数不断堆积 → 负载飙升,CPU 使用率却很低
注:D状态就是不可中断睡眠进程,也就是进程睡死了,kill -9 pid也没办法杀死它
场景 2:NFS / 网络存储卡住
- NFS 服务器超时
- 云盘网络抖动
- 挂载了不可达的远程目录
此时,负载飙到几十甚至上百,CPU idle 90%+,top 里一堆进程是 D 状态
场景 3:进程在等锁 / 内核资源
- 内核 mutex 卡死
- cgroup 锁竞争
- 某些内核 bug
此时,负载高,CPU 不高,vmstat 里 cs 很高
场景 4:大量不可中断 I/O(D 状态)
ps aux | awk '$8 ~ /D/ {print}' #如果看到一堆 D 状态进程 → 几乎可以确诊是 I/O 问题
2、如何判断是哪种情况?
✅ 第一步:top 看三个关键指标
- load average:高
- %Cpu(s) us/sy:低
- %Cpu(s) wa(iowait):高 ✅ 注:wa 高 = I/O 在拖后腿
✅ 第二步:vmstat 1 确认
| 列 | 含义 | 异常表现 |
|---|---|---|
r | 等 CPU 的进程 | 高 = CPU 瓶颈 |
b | 等 I/O 的进程 | 高 = I/O 瓶颈 ✅ |
wa | I/O wait | 高 |
si/so | swap | 非 0 = 内存问题 |
👉 b 高 + wa 高 = 负载高 CPU 不高的铁证
✅ 第三步:iostat 看磁盘
iostat -xz 1
- %util → 接近 100% = 磁盘被打满
- await → 毫秒级上升 = 磁盘慢
- r/s w/s → 读写 IOPS
✅ 第四步:找是谁在搞 I/O
iotop -o #可看到哪个进程读多少写多少
pidstat -d 1 #可看到哪个进程读多少写多少
什么是D状态?
D 状态全称就是 “Uninterruptible Sleep”(不可中断睡眠)
简单说: 进程睡死了,连 kill -9 都叫不醒它,不是权限不够,不是信号没到——是内核根本不允许它处理信号,D 状态进程只能由内核代码主动调用唤醒函数把它移回可运行状态,不可能由任何外部信号或用户操作直接唤醒
为什么设计成”不可中断”?
这不是 bug,是故意的,原因很硬核,场景还原如下:
- 进程调用
read()读一个文件 - 内核向磁盘驱动下发 I/O 请求
- 但此时磁盘正在干活,硬件手里攥着进程的请求(磁盘控制器 / DMA 引擎手里拿着进程的”内存地址”,准备往里写数据)
- 这时候如果进程被信号杀掉/内核允许这个时候D状态被唤醒
- 磁盘 I/O 完成后,往一个已经不存在的进程地址写数据(已杀掉)或者数据不完整(提前唤醒D状态)
👉 轻则数据写飞,重则内核崩溃
所以内核做了一个”铁律”:
- 只要进程在等内核认为”必须完成”的事情,就不允许被中断,这就是 D 状态的本质:进程被”钉死”在内核态,等一个必须完成的 I/O 操作结束
D 状态 vs S 状态(可中断睡眠)区别:
| D 状态 | S 状态 | |
|---|---|---|
| 全称 | Uninterruptible Sleep | Interruptible Sleep |
| 能被 kill 吗 | ❌ 不能 | ✅ 能 |
| 典型场景 | 等磁盘 I/O、NFS、某些锁 | 等网络数据、定时器等 |
| 信号响应 | 不响应任何信号 | 响应信号 |
| 是否计入负载 | ✅ 计入 | ❌ 不计 |
D 状态为什么会让负载飙升?
因为负载 = R 状态 + D 状态进程数,D 状态进程不算在运行,但算在”排队等系统资源”里
正常: R=2, D=0 → 负载 ≈ 2
异常: R=2, D=50 → 负载 ≈ 52 ← 看着吓人,但 CPU 可能很闲
所以你看到负载 50+ 但 CPU idle 90%——就是一堆进程卡在 D 状态等 I/O
D 状态会持续多久?
| 情况 | 持续时间 |
|---|---|
| 正常磁盘读 | 几毫秒到几十毫秒 |
| 慢磁盘 / 高负载 | 几百毫秒到几秒 |
| NFS 超时(默认) | 几分钟(默认 timeo=600,即 60 秒) |
| 磁盘彻底挂了 | 永远不返回(进程永久 D 状态) |
最后一种最坑——进程永远卡在 D 状态,只能重启
看系统里有哪些 D 状态进程?
ps aux | awk '$8 ~ /D/ {print $2, $8, $11}'
或者更详细:
for pid in $(ls /proc | grep '^[0-9]'); do
if [ -r /proc/$pid/stat ]; then
state=$(awk '{print $3}' /proc/$pid/stat)
if [ "$state" = "D" ]; then
echo "PID: $pid CMD: $(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')"
fi
fi
done
遇到大量 D 状态怎么办?
先看在等什么 I/O
cat /proc/<PID>/stack # 看内核调用栈,能看到卡在哪个函数
- io_schedule → 等磁盘 I/O
- nfs_wait_bit_killable → 等 NFS
- futex_wait_queue → 等锁(极少数情况)
查磁盘是否健康
dmesg | grep -i "error\|timeout\|reset"
smartctl -a /dev/sda
如果是 NFS
mount | grep nfs
# 看是否有 soft/hard、timeo 参数
如果 D 状态进程大量堆积且磁盘无响应:只能重启服务器


