CPU跑满几乎是每个做后端开发和运维的人都遇到过的问题。表现往往很突然:网站响应变慢、SSH登录卡顿、定时任务堆积,登上服务器一看,CPU使用率已经顶在100%下不来。这时候盲目重启服务只能缓解一时,真正要做的是快速定位到底是哪个进程、哪个线程、哪段代码在消耗CPU。Linux下有一套成熟的排查工具链,top负责快速概览,htop负责细看线程分布,perf负责深入函数级别采样,三者配合使用,基本能覆盖绝大多数高CPU场景。

一、用top快速锁定问题进程
top是最经典也最普及的系统监控工具,几乎所有Linux发行版都自带,无需安装。登上服务器后第一件事就是敲一个top,重点看两个区域:顶部的系统负载汇总和底部的进程列表。
在顶部的CPU行中,有几个字段必须看懂。us表示用户态CPU,如果它很高,说明是应用程序自己的代码在消耗CPU,比如业务逻辑中的循环计算、字符串处理、序列化反序列化等;sy表示内核态CPU,过高通常意味着系统调用频繁,比如程序在疯狂读写文件或创建销毁线程;si是软中断,网络包处理过多时会明显上升,常见于高流量场景;wa是等待IO的CPU,它高说明瓶颈其实在磁盘而不是CPU本身;st则在虚拟机环境里出现,代表被宿主机偷走的时间,云服务器上如果st持续偏高,说明物理机超卖了或者邻居虚拟机太吵。
看完整体指标,再按大写P键按CPU占用排序,找出占用最高的进程。此时需要进一步看线程:在top界面按大写H,或者直接执行top -H,进程列表会切换成线程视图,这样能看到进程内部哪个线程ID占用最高。对于Java应用,拿到线程ID后可以通过printf '%x'换算成十六进制,再去jstack输出的线程dump里搜索 nid=0x对应的值,直接定位到具体的Java线程栈,死循环、正则回溯灾难、频繁GC这些问题都能在这一步现出原形。
二、用htop看更清晰的线程与负载细节
htop可以理解为top的增强版,界面更直观,支持鼠标操作、彩色显示和树状视图。很多发行版默认没有安装,可以通过包管理器补上,比如CentOS下的yum install htp或Ubuntu下的apt install htop。
相比top,htop的优势在于信息呈现方式。底部的CPU柱状图按核心独立显示,一眼就能看出是单核跑满还是多核均匀跑满。如果只有一两个核心顶到100%,大概率是单线程程序的计算瓶颈或者某个线程陷入死循环;如果所有核心都跑满,则更可能是流量压力过大、并发任务过多,或者程序本身被设计成了多线程满负荷工作。
在实际排查时,还可以按F4输入关键字过滤进程,按F5切换树状视图查看父子进程关系,确认是不是某个脚本疯狂fork出了大量子进程。另外htop支持直接对进程发送信号,按F9可以选择SIGTERM或SIGKILL,处理僵尸进程或失控进程时不用切回命令行敲kill命令,操作效率更高。建议把htop当作top之后的第二步,用它确认线程级别的CPU分布情况,为后续的深入分析缩小范围。
三、用perf深入函数级热点分析
当top和htop只告诉你哪个进程吃掉了CPU,却说不清具体是哪段代码在吃时,就该perf出场了。perf是Linux内核自带性能计数器工具的封装,通过采样方式统计CPU时间花在了哪些函数上,是定位性能瓶颈的终极手段。
最常用的命令是perf top,它实时显示系统中占用CPU最高的函数排行,类似函数级别的top。如果已经锁定了目标进程,可以用perf record -g -p 进程号采样几秒到几十秒,-g参数会记录调用栈,然后用perf report查看结果。报告中按占用比例排序的函数列表,配合调用链,基本能看清程序的执行热点在哪里。比如发现某个日志格式化函数占了40%的CPU,那大概率是日志级别配置过低或日志量过大;发现内存分配函数占主导,就要怀疑频繁创建对象、缺少对象池之类的问题。
使用perf前需要确认内核支持,云服务器上部分镜像默认关闭了性能计数器,遇到权限报错时可以检查/proc/sys/kernel/perf_event_paranoid的值,设为1或0通常就能采样。符号方面,如果看到一堆十六进制地址而不是函数名,说明程序没有携带符号表,C/C++程序编译时加-g参数、Java程序配合映射文件才能解析出可读的函数名。
四、常见高CPU原因与处理思路
工具会用只是基础,更重要的是把结论映射到正确的处理动作上。下面这张表汇总了常见场景:
| 现象特征 | 常见原因 | 处理方向 |
|---|---|---|
| us高,单线程占用突出 | 代码死循环、正则回溯、低效算法 | perf定位热点函数,修改代码逻辑 |
| us高,多线程均匀占用 | 流量暴涨、并发设置过高 | 限流、扩容、优化处理逻辑 |
| sy高,系统调用频繁 | 频繁小IO、大量线程创建销毁 | 加缓存、改批量写、用线程池 |
| si高,软中断突出 | 网络包处理量大 | 开启网卡多队列、调整中断亲和性 |
| wa高,IO等待严重 | 磁盘性能不足 | 更换SSD或更高性能云盘 |
| st高,虚拟化偷取 | 宿主机资源争抢 | 更换独享型实例或迁移 |
除了表中的场景,Java服务还常见GC导致的CPU飙升,可以用jstat -gcutil观察垃圾回收频率,如果Full GC每隔几秒一次,优先排查内存泄漏或堆配置不合理。Python服务则要注意GIL的存在,多线程未必能利用多核,CPU密集任务应改用多进程。
总结一下排查顺序:top看全局找进程,htop看线程分细节,perf看函数找根因,中间结合strace、jstack、日志分析交叉验证。养成先定位再动手的习惯,避免盲目重启掩盖真正的病灶,才能让问题被彻底解决而不是反复发作。