Linux系统常见CPU内存问题排查

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 1cs 列
内存不足free -havailable
内存泄漏pidstat -rRSS 趋势
I/O 瓶颈iostat -xz 1%util、await
D 状态ps aux \| awk '$8~/D/'D 状态进程
OOMdmesg \| grep -i oomkilled 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 瓶颈​ ✅
waI/O wait高
si/soswap非 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 SleepInterruptible 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 状态进程大量堆积且磁盘无响应:只能重启服务器