单核CPU打满而整体利用率低,最常见原因是某线程陷入无限循环或密集计算;需用mpstat -P ALL 1定位持续高负载核心,并深入线程级、栈帧级和调度行为分析。

单核 CPU 打满(比如某个 core 长期 100%),而整体 CPU 利用率很低,最常见原因之一就是某一线程陷入无限循环或持续密集计算——它不阻塞、不等待、不释放 CPU,只在一个核心上“钉死”运行。识别这类问题,关键不是看总负载,而是穿透到线程级、栈帧级、调度行为级。
看实时线程分布:确认是不是真有线程“卡死”在单核
先用 mpstat -P ALL 1 每秒刷新各核心使用率,确认是否出现“一个 core 持续 95%+,其余 core
- top -H -p $PID:进入线程视图,按 P 按 CPU 使用率排序,找长期排第一、且 %CPU 接近 100 的线程(注意是线程,不是进程)
- htop -p $PID,按 H 切换线程模式,更直观看到哪个 LWP(轻量级进程)持续高亮
- 记下该线程的十进制 TID(如 23456),转为十六进制(printf "%x\n" 23456 → 5ba0),后续用于堆栈匹配
抓线程堆栈:定位具体在哪行代码“空转”
对疑似高 CPU 线程做快照分析,避免靠猜:
- Java 应用:jstack $PID | grep -A 20 "nid=0x5ba0",重点看 java.lang.Thread.State: RUNNABLE 且堆栈末尾落在某行循环逻辑(如 while(true)、calculate()、parseLoop())
- Go 应用:go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30,生成火焰图,顶部宽而深的函数即热点;也可用 curl "http://localhost:6060/debug/pprof/goroutine?debug=2" 查看所有 goroutine 栈,找大量重复调用同一函数的
- 通用方法:perf record -p $PID -g -- sleep 10 && perf report -g,直接捕获内核+用户态调用链,无需语言支持
区分真假“忙”:排除自旋等待和锁竞争干扰
不是所有 RUNNABLE 状态都等于“有效计算”。要判断是不是真在干活:
- 如果堆栈显示卡在 Unsafe.park、Object.wait、LockSupport.park —— 实际是空等,不算 CPU 消耗源
- 如果卡在 synchronized 块入口、ReentrantLock.lock(),且多个线程 waiting to lock 同一地址 → 是锁竞争,不是无限循环
- 真正可疑的是:堆栈稳定、无系统调用、反复调用同一业务方法(尤其含 for/while、递归、正则匹配、JSON 解析等易失控操作)
验证与收口:加监控、设基线、防复发
定位后别急着改代码,先做两件事验证结论:
- 用 kill -3 $PID(Java)或 kill -SIGUSR1 $PID(Go)触发一次线程 dump,对比前后是否同一 TID 持续出现在 top 顶端
- 临时限制该进程只能跑在单核:taskset -c 0 $CMD,若此时整体 CPU 反而下降、响应变好 → 坐实是单线程瓶颈,非调度异常
- 上线前建立基线:用 pidstat -t -p $PID 1 记录正常时线程数、平均 CPU/线程、上下文切换频率,便于下次快速比对










