CPU 跑满了怎么排查?从 top 到进程栈的定位思路
CPU 跑满不等于一定是程序死循环。
排查的第一步是确认 CPU 时间被谁消耗掉,再决定是杀进程、改配置还是查业务代码。
下面按 Linux 服务器常见场景,把定位顺序和判断依据讲清楚。
先确认是哪种“满”
登录服务器后先跑 top,看最上面那行 %Cpu(s):
top
重点关注三个值:
us用户态占用高:业务代码在计算,通常是死循环、正则回溯、大量序列化或爬虫抓取。sy内核态占用高:系统调用频繁,常见于大量小文件读写、网络包处理、容器或虚拟化开销。waIO 等待高:CPU 其实在等磁盘,看起来忙但并不是算力被吃满。
如果 wa 明显偏高,排查方向应转向磁盘和 IO,而不是继续盯着 CPU 进程。
找到占用最高的进程和线程
在 top 界面按 P 按 CPU 排序,记下排在前面的 PID。
接着用 pidstat 看这个进程的线程级占用:
pidstat -u -t -p 1 5
输出里 TID 列就是线程号。
多线程程序往往只有一个或几个线程跑满,比如 Java 的 GC 线程、Nginx 的 worker、PHP-FPM 的某个子进程。
把线程号转成十六进制,方便后面查栈:
printf '%x\n'
这一步的意义是把“某个进程占用高”缩小到“某个线程在干什么”,否则直接看进程级数据很难判断问题。
用 perf 看热点函数
确认是用户态占用高后,可以用 perf 采样,看 CPU 时间花在哪些函数上:
perf top -p
如果系统没装 perf,Debian/Ubuntu 用 apt install linux-perf,CentOS 用 yum install perf。
采样结果里排在前面的符号,通常就是真正的热点。
比如大量时间落在字符串匹配函数,可能是正则写得太宽;
落在锁相关函数,可能是线程争抢。
对于短时突发,也可以先记录再分析:
perf record -p -g -- sleep 30
perf report
结合进程栈判断业务原因
采样只能看到函数名,要对应到业务逻辑还得看调用栈。
Java 进程可以用 jstack 导出线程栈,重点看状态为 RUNNABLE 且占用高的线程在做什么。
PHP-FPM 可以看慢日志,Nginx 可以看 access log 里是否有异常高频请求。
如果是脚本类进程,比如 Python 或 Shell,先确认是不是定时任务重叠执行:
crontab -l
ps -ef | grep <脚本名>
同一脚本被多次拉起,是 CPU 跑满里很常见又容易被忽略的原因。
临时止损与复查
定位清楚前,如果业务已经受影响,可以先把异常进程降优先级而不是直接 kill:
renice 10 -p
确实要停止时,先确认它是否承担关键职责,再执行 kill ,并观察 top 中 us 是否回落。
处理完成后隔一段时间复查一次:
pidstat -u -p 1 10
top -b -n 1 | head -20
如果占用反复出现,说明只是暂时压下去了,需要回到 perf 采样和业务日志继续找根因。
常见误判
- 只看总 CPU 百分比,不看
us/sy/wa的分布,容易把 IO 问题当成计算问题。 - 看到某个进程占用高就直接重启服务,重启后短时间正常,过一阵又复现。
- 忽略容器环境:在容器里
top看到的是宿主机视角,需要结合docker stats或 cgroup 限制判断。 - 把负载高等同于 CPU 满,实际上负载还包括不可中断的 IO 进程。
CPU 跑满的排查核心是逐层缩小范围:先分清 us、sy、wa,再定位进程和线程,最后用采样和调用栈对应到具体代码或配置。
按这个顺序走,大多数问题都能找到可以验证的原因。