
1. 问题引入当你的Linux服务器开始“喘粗气”如果你运维过线上服务器或者长期在Linux环境下开发一定遇到过这种让人心头一紧的时刻终端响应变慢敲个命令半天才出结果甚至直接卡死。这时候你的第一反应很可能是——CPU是不是被什么程序吃光了没错CPU使用率过高是Linux系统中最常见也最棘手的性能问题之一。它不像内存泄漏那样有明确的“OOM Killer”来终结进程也不像磁盘IO瓶颈那样有iostat可以直观看到等待队列。CPU高负载更像是一种“无声的警报”背后可能是一个正在疯狂计算的科学模拟程序也可能是一个陷入死循环的Bug甚至是一个隐蔽的挖矿木马。我处理过无数次线上告警从电商大促时订单处理服务CPU飙到100%到数据库服务器因糟糕的SQL查询而“高烧不退”。每次排查都是一次从现象到本质的侦探游戏。网上有很多零散的命令介绍比如“用top看哪个进程CPU高”但这只是第一步。真正的挑战在于当你看到top里某个进程的CPU使用率高达300%在多核系统上CPU使用率可以超过100%它表示占用了多个核心的算力时你该如何继续深挖是这个进程的哪个函数在作祟是系统调用太频繁还是锁竞争太激烈是代码逻辑问题还是系统配置不当这篇文章我就结合自己踩过的坑和总结的方法论带你走一遍完整的Linux CPU使用率过高排查流程。我们不只讲命令怎么用更重点讲清楚命令输出的每一行数字背后意味着什么以及看到一个异常值后你的排查思路应该往哪个方向延伸。目标是让你下次再遇到“CPU告警”时能像老中医一样望闻问切快速定位病根。2. 排查工具箱从宏观到微观的武器库排查CPU问题不能指望一个命令包打天下。我们需要一套层次分明的工具链从全局概览到进程剖析再到函数级的热点分析。下面这个表格梳理了不同排查阶段的核心工具及其关键作用你可以把它当作一张“寻宝地图”。排查阶段核心工具主要作用关键输出信息全局概览与初步定位top/htop实时查看系统整体负载、CPU使用率、内存状态并快速识别高CPU消耗的进程。%Cpu(s)行、PID、%CPU、COMMANDuptime/w查看系统平均负载Load Average判断系统繁忙程度的宏观指标。load average: 1.23, 0.86, 0.65vmstat查看系统级别的CPU、内存、IO等资源在时间间隔内的统计信息。r就绪队列长度、us用户态CPU、sy系统态CPU进程级深度剖析ps静态或动态抓取进程快照支持丰富的过滤和格式化输出用于精准定位。ps -eo pid,pcpu,pmem,comm --sort-pcpu | headpidstat监控指定进程或所有进程的CPU、内存、IO等详细统计可设置采样间隔。%usr、%system、%guest、CPU进程绑定的核心线程级观察top -H/htop线程模式查看单个进程内所有线程的CPU使用情况定位“问题线程”。线程PIDLWP、线程%CPUps -eLf列出所有线程信息可配合grep过滤特定进程的线程。NLWP线程数、LWP线程ID、PSR运行CPU调用链与热点分析perfLinux内核提供的性能分析神器可进行CPU性能计数器采样生成火焰图。perf top实时热点、perf record录制、perf report分析strace/ltrace跟踪进程的系统调用或库函数调用适用于分析频繁IO或锁导致的CPU等待。strace -cp pid统计调用次数和时间上下文与调度分析sar系统活动报告器可收集、报告和保存系统活动信息用于历史回顾和趋势分析。sar -u 1 3CPU使用率、sar -q队列长度mpstat查看每个CPU核心的详细使用情况诊断是否负载不均。%usr、%nice、%sys、%iowait、%irq、%soft、%steal、%idle提示htop是top的增强版支持颜色、鼠标操作和树状视图直观性更好建议安装使用yum install htop或apt install htop。perf功能强大但需要一定学习成本是定位性能瓶颈的终极武器之一。有了工具箱的全局认识我们接下来就按照实际排查的逻辑顺序一步步深入。3. 第一步全局视野快速锁定嫌疑范围当告警响起你需要像指挥官一样首先获得战场的全局态势。盲目地ps aux然后grep是低效的。3.1 理解“平均负载”系统压力的晴雨表登录服务器后别急着敲复杂的命令。先看一眼uptime或w$ uptime 16:30:45 up 30 days, 2:15, 3 users, load average: 8.23, 5.67, 3.89最后三个数字8.23, 5.67, 3.89就是著名的平均负载。它们分别代表过去1分钟、5分钟、15分钟内系统**处于可运行状态R和不可中断睡眠状态D**的平均进程数。简单理解就是CPU的“待办任务队列”长度。如何解读假设你的服务器是4核CPU。如果1分钟负载是8.23意味着平均有8.23个任务在排队等待CPU这已经超过了4个核心的处理能力说明系统在过去1分钟非常繁忙。而15分钟负载是3.89小于4说明系统在更长时间尺度上压力尚可但近期1分钟内出现了突发的高负载。核心经验平均负载高不一定代表CPU使用率高。如果大量进程处于不可中断睡眠D状态通常是等待磁盘IO也会导致负载升高但此时CPU可能很闲%iowait高。所以负载是一个综合压力指标需要结合CPU使用率具体分析。3.2 使用 top/htop 进行实时诊断接下来打开top这是主战场。top界面信息密集我们聚焦几个关键点首部汇总信息top - 16:32:58 up 30 days, 2:17, 3 users, load average: 8.23, 5.67, 3.89 Tasks: 231 total, 2 running, 229 sleeping, 0 stopped, 0 zombie %Cpu(s): 75.3 us, 22.5 sy, 0.0 ni, 1.8 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st%Cpu(s)行是核心中的核心ususer用户态CPU时间占比。高通常意味着应用程序自身在疯狂计算。sysystem内核态CPU时间占比。高可能意味着系统调用频繁、上下文切换过多、或者内核在处理中断。ididle空闲CPU占比。这个值越低说明CPU越忙。waiowait等待IO的CPU时间占比。这是关键指标如果us和sy都不高但wa很高说明瓶颈在磁盘IOCPU在空等这时候去优化计算逻辑是南辕北辙。ststeal在虚拟化环境中被宿主机“偷走”的CPU时间。如果这个值持续很高说明你的虚拟机所在的物理主机资源竞争激烈。进程列表默认按CPU使用率排序。重点关注PID进程号。%CPU进程占用CPU百分比。在多核系统中这个值可以超过100%。比如一个单线程进程在4核机器上最多占用100%而一个多线程进程可能占用400%。COMMAND进程名或命令行。有时这里显示的是java你需要结合PID进一步分析是哪个Java应用。在top中常用的交互命令P按CPU使用率排序默认。M按内存使用率排序。1展开显示所有CPU核心的单独使用情况。这对于诊断多核负载是否均衡非常有用。如果只有一个核心跑满100%其他核心很闲那可能是进程或线程的CPU亲和性设置有问题或者就是单线程应用。H切换显示线程。这是关键一步在top中按H后进程列表会变成线程列表。原来一个%CPU很高的Java进程现在你可以看到是其中的哪个线程通常显示为PID其实是LWP轻量级进程ID在消耗CPU。记下这个线程的PIDLWP。实操心得我习惯先用htop因为它颜色区分更明显而且支持鼠标点击表头排序。在htop里按F2进入设置在“Display options”里可以开启“Tree view”以树状形式显示进程和线程的父子关系对于分析像Java这类多线程应用特别直观。4. 第二步深入进程与线程揪出元凶通过top/htop我们找到了消耗CPU最高的进程假设PID是12345以及其内部的某个高CPU线程假设LWP是12346。接下来我们要给这个“嫌疑犯”做一次深度体检。4.1 使用 pidstat 进行进程资源监控pidstat是sysstat工具包的一部分能提供比top更细致的进程级资源统计。如果系统没有请安装yum install sysstat/apt install sysstat。# 每1秒采样一次共采样5次监控PID为12345的进程 $ pidstat -p 12345 1 5 Linux 5.4.0-... 04/10/2024 _x86_64_ (4 CPU) 04:20:00 PM UID PID %usr %system %guest %wait %CPU CPU Command 04:20:01 PM 1001 12345 85.00 12.00 0.00 0.00 97.00 2 java 04:20:02 PM 1001 12345 86.00 11.00 0.00 0.00 97.00 2 java ...%usr进程在用户态的执行时间占比。对应top中的us。%system进程在内核态的执行时间占比。对应top中的sy。%wait进程等待运行时即在就绪队列中的时间占比。如果这个值高说明进程已经准备好运行但CPU没空可能是进程太多或优先级问题。CPU进程最后运行在哪个逻辑CPU核心上。结合mpstat可以看该核心是否饱和。更强大的用法结合-t参数查看线程信息。$ pidstat -t -p 12345 1 3 ... 04:21:00 PM UID TGID TID %usr %system %guest %wait %CPU CPU Command 04:21:01 PM 1001 12345 - 84.00 13.00 0.00 0.00 97.00 2 java 04:21:01 PM 1001 - 12346 83.00 13.00 0.00 0.00 96.00 2 |__java这里TGID是线程组ID即主进程PIDTID是线程ID。可以清晰看到线程12346几乎消耗了该进程所有的CPU时间。这印证了我们之前在top -H中的发现。4.2 分析进程状态与资源限制知道了是哪个进程和线程我们还需要看看它是否被限制或者处于什么状态。# 查看进程的详细状态和资源限制 $ cat /proc/12345/status Name: java State: R (running) # 状态R-运行S-睡眠D-不可中断睡眠Z-僵尸 ... voluntary_ctxt_switches: 1250 nonvoluntary_ctxt_switches: 580 # 非自愿上下文切换次数如果很高说明CPU竞争激烈 ... # 查看进程的CPU亲和性允许在哪些CPU核心上运行 $ taskset -cp 12345 pid 12345s current affinity list: 0-3 # 允许在0,1,2,3号核心上运行如果nonvoluntary_ctxt_switches非自愿上下文切换数值增长极快说明操作系统频繁地强行剥夺该进程的CPU时间片给其他进程这本身也会消耗CPU资源形成恶性循环。5. 第三步代码级洞察定位热点函数找到了问题线程但它是执行什么代码这么耗CPU呢是业务逻辑的复杂计算还是在空转循环这时候就需要perf这样的神器出场了。5.1 使用 perf top 进行实时热点采样perf top类似于全局的top但它是函数级别的。$ sudo perf top Samples: 1M of event cpu-clock, Event count (approx.): 25912345678 Overhead Shared Object Symbol 45.62% [kernel] [k] _raw_spin_unlock_irqrestore 22.31% libc-2.31.so [.] __memmove_avx_unaligned_erms 15.87% myapp [.] MyClass::expensiveCalculation 8.12% [kernel] [k] finish_task_switch ...Overhead该符号函数占用的CPU时间比例。Shared Object函数所在的模块可执行程序、动态库或内核。Symbol函数名。如果显示为[.]开头且是十六进制地址可能是没有调试符号需要安装-dbgsym包或编译时加上-g选项。在上面的例子中我们看到内核函数_raw_spin_unlock_irqrestore占了近一半开销这强烈暗示着锁竞争激烈。可能是用户态程序频繁调用某些系统调用导致内核锁争用。libc的memmove函数占比较高可能意味着存在大量的内存拷贝操作。我们自己的应用函数MyClass::expensiveCalculation也占了不少CPU这是预期的业务逻辑。注意事项perf top默认对所有进程采样。为了更精确我们可以用-p选项指定我们怀疑的进程PIDsudo perf top -p 12345这样采样就只针对该进程结果更清晰。5.2 使用 perf record/report 生成详细分析报告perf top是实时的但数据流走了就没了。为了更深入分析我们可以录制一段时间的数据然后生成可交互的报告。# 对PID为12345的进程采样30秒的CPU调用栈 $ sudo perf record -F 99 -g -p 12345 -- sleep 30 [ perf record: Woken up 8 times to write data ] [ perf record: Captured and wrote 2.345 MB perf.data (58423 samples) ] # 生成报告 $ sudo perf report -n --stdio-F 99表示每秒采样99次Hz。-g表示记录调用栈call graph这对生成火焰图至关重要。报告是交互式的你可以按方向键浏览回车键展开调用链。它会清晰地展示出从底层系统调用到上层业务函数的完整调用路径以及每条路径的CPU时间占比。5.3 生成火焰图性能问题的“X光片”火焰图是perf数据最直观的展现方式。它由Brendan Gregg推广能一眼看出CPU“火”在哪里。录制数据同上使用perf record。生成折叠堆栈文件$ sudo perf script -i perf.data out.perf $ ./FlameGraph/stackcollapse-perf.pl out.perf out.folded需要先下载Brendan Gregg的FlameGraph工具包生成SVG火焰图$ ./FlameGraph/flamegraph.pl out.folded cpu_flamegraph.svg用浏览器打开这个SVG文件。看火焰图的关键X轴表示采样到的调用栈数量总和不是时间顺序。Y轴表示调用栈的深度顶层是正在执行的函数下层是它的父函数。颜色通常没有特殊含义只是为了区分。如何看寻找最宽的“火峰”。哪个函数在X轴上占据的宽度最大它就是CPU消耗的“热点”。将鼠标悬停在色块上会显示具体的函数名和采样百分比。如果火焰图顶部是平顶的像一堵墙那很可能就是空循环或者自旋锁。如果看到大量时间花在malloc、free或pthread_mutex_lock上那瓶颈可能就是内存分配或锁竞争。6. 第四步专项排查与经典场景分析除了通用的性能分析工具针对一些特定的高CPU场景我们有更精准的排查手段。6.1 排查频繁系统调用与IO等待如果top显示%sy系统态CPU很高或者%waIO等待很高可以使用strace来跟踪进程的系统调用。# 统计PID为12345的进程在5秒内的系统调用 $ sudo strace -cp 12345 strace: Process 12345 attached ^C strace: Process 12345 detached % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 95.12 5.123456 1023 5000 futex 3.21 0.172834 34 5000 read 1.67 0.089765 17 5000 write这个输出非常有用它显示futex快速用户态互斥锁调用占了95%的时间并且有5000次调用。这直接证实了锁竞争是罪魁祸首。futex是pthread_mutex等锁机制的底层实现高频的futex调用意味着线程在频繁地争抢和等待锁。对于怀疑是IO瓶颈%wa高的情况可以结合iostat和strace一起看$ iostat -x 1 # 查看磁盘IO详细状态关注%util设备利用率和await平均等待时间 $ sudo strace -T -e tracefile -p 12345 # 跟踪进程的文件操作并显示每次调用的耗时6.2 排查Java应用的高CPU问题Java应用的高CPU排查有其特殊性。除了上述通用方法JVM自带的工具是首选。jstack获取线程堆栈$ jstack -l 12345 jstack_dump.txt在输出的堆栈文件中搜索之前用top -H或pidstat -t找到的高CPU线程的十六进制ID比如LWP12346的十六进制是0x303a。找到对应的线程堆栈看它卡在哪个Java方法、哪行代码上。常见的模式有RUNNABLE状态正在执行某个计算密集型方法。BLOCKED状态在等待锁显示waiting to lock 0x0000000712345678。WAITING/TIMED_WAITING状态在Object.wait()或sleep。jstat查看GC情况$ jstat -gcutil 12345 1000 5 # 每1秒采样一次GC信息共5次关注FGCFull GC次数和FGCTFull GC时间。如果FGC频繁发生且FGCT很长说明垃圾回收消耗了大量CPU时间可能是内存泄漏或堆大小设置不合理。jmapMAT分析堆内存如果怀疑是内存问题导致的频繁GC可以用jmap导出堆内存快照然后用Eclipse Memory Analyzer (MAT)工具分析找出占用内存最多的对象和引用链。6.3 排查容器环境下的CPU问题在Docker或Kubernetes环境中由于top和ps看到的是宿主机视角直接定位容器内进程会有些麻烦。进入容器命名空间# 找到容器在宿主机上的PID $ docker inspect --format {{.State.Pid}} container_name 12345 # 使用nsenter进入容器的进程、网络等命名空间 $ nsenter -t 12345 -n -p # 此时再运行top/ps看到的就是容器内的视图了使用crictl针对K8s CRI运行时$ crictl ps | grep pod_name # 找到容器ID $ crictl stats container_id # 查看容器资源统计关键点容器CPU限制cpu-quota和cpu-period会影响top中%CPU的显示。一个在容器内显示占用100%CPU的进程在宿主机上可能只占用了10%的物理CPU。排查时需结合docker stats或kubectl top pod的指标来看。7. 常见问题场景与速查手册根据我的经验CPU高负载无外乎几种常见模式。下面这个表格可以帮助你快速对号入座并给出下一步排查方向。现象组合可能原因下一步排查命令/方向%us高%sy低应用程序业务逻辑计算密集。可能是算法复杂度高、死循环、正则表达式灾难性回溯等。1.perf top -p pid定位热点函数。2.jstackJava或gdbC/C查看线程堆栈。3. 检查应用日志看是否有大量重复计算或异常循环。%sy高%us低系统调用频繁或内核态工作多。可能是短连接频繁创建销毁、大量小文件IO、锁竞争导致上下文切换激增。1.strace -cp pid统计系统调用。2.pidstat -w 1查看上下文切换次数cswch/s自愿切换nvcswch/s非自愿切换。3.perf top查看内核函数热点如_raw_spin_lock。%wa高IO瓶颈。CPU在等待磁盘或网络IO。可能是磁盘慢、数据库查询慢、远程调用超时等。1.iostat -x 1查看磁盘%util和await。2.sar -n DEV 1查看网络流量。3. 检查应用日志中的超时错误或使用strace跟踪read/write/sendto/recvfrom调用。%si或%hi高软中断或硬中断处理占用高。可能是网络包处理如高PPS、定时器中断等。1.cat /proc/interrupts查看中断在各CPU核心的分布。2.sar -n DEV 1查看网络包速率rxpck/s,txpck/s。3. 检查是否有异常的定时任务或内核模块。单个核心100%其他核心空闲单线程应用或进程/线程CPU亲和性设置问题。1.taskset -cp pid查看进程的CPU亲和性。2. 检查程序是否是单线程设计。3. 考虑使用taskset或numactl将进程绑定到多个核心或优化程序为多线程。Load Average高但CPU%id不低进程处于不可中断睡眠D状态通常是在等待IO。也可能是僵尸进程过多。1.top查看是否有进程状态为D。2.dstat 1或iotop查看磁盘IO情况。3.ps aux | grep defunct查找僵尸进程。Java进程CPU高GC频繁、死循环、锁竞争。1.jstack pid查看线程堆栈寻找RUNNABLE或BLOCKED的线程。2.jstat -gcutil pid 1000查看GC频率和耗时。3. 使用jmap和MAT分析堆内存。8. 实战案例复盘一次由“正则表达式”引发的CPU血案最后分享一个让我印象深刻的真实案例。线上一个API服务突然CPU告警top显示一个Java进程CPU使用率持续在250%以上8核机器。初步定位pidstat -t -p pid显示有一个线程几乎独占所有CPU。线程堆栈jstack找到该线程堆栈显示它卡在java.util.regex.Matcher.find()方法里。代码检查检查对应的代码发现是一个处理用户输入的正则表达式^([a-z])*$用来验证字符串是否由小写字母组成。看起来没问题问题根源当输入是aaaaaaaaaaaaaaaaaaaaaaaa!一串a加一个感叹号时这个正则表达式陷入了灾难性回溯。由于([a-z])*存在指数级的匹配可能性在匹配失败前会进行巨量的回溯计算瞬间吃满CPU。解决方案将正则表达式重写为^[a-z]$去掉冗余的分组和量词。修复后CPU使用率立刻降至正常水平。这个案例的教训CPU高不一定是“计算量大”可能是“无效计算多”。正则表达式、递归算法、复杂字符串匹配等都是容易引发性能黑洞的地方。工具jstack只能告诉你“在哪里”不能告诉你“为什么”。结合代码逻辑分析才是根本。排查CPU问题本质上是一个“大胆假设小心求证”的过程。从宏观指标入手层层下钻结合对系统原理和应用架构的理解最终定位到那一行有问题的代码。这套方法论和工具链就是你在Linux性能世界里披荆斩棘的利剑。