CPU持续100%但strace无高频系统调用,说明是用户态忙循环或密集计算;需用top -H、ps、/proc/PID/stat确认线程级负载,pstack/gstack抓堆栈定位热点函数,perf record生成火焰图精确定位到源码行,并排查编译优化、自旋锁、JIT等环境因素。

当 top 显示 CPU 使用率持续 100%,但用 strace -p PID 跟踪却看不到高频、耗时的系统调用(比如没有大量 read/write/epoll_wait),说明问题大概率不在 I/O 等待或外设交互上,而是进程本身在用户态疯狂循环或密集计算——也就是“纯 CPU 型热点”,strace 对它基本“视而不见”,因为它不触发系统调用。
确认是否真为用户态忙循环
先排除干扰项,确保观察的是真实负载:
- 运行
top -H -p,看是单个线程还是多个线程占满 CPU;若只有一个线程长期 99%+,重点盯它 - 执行
ps -mo pid,tid,%cpu,comm -p,核对线程 ID(TID)和 CPU 占比,记下高占用线程的 TID - 用
cat /proc/查第 14 字段(utime)和第 15 字段(stime),连续采样几秒,看 utime 是否飞速增长——大幅增长说明确实是用户态代码在狂跑/stat
用 pstack 或 gstack 抓线程堆栈快照
strace 看不到的忙循环,在函数调用栈里往往一目了然。只要进程还在运行,就能快速抓取当前执行位置:
- 对高 CPU 线程 TID 执行:
pstack(或gstack),输出类似:#0 0x00007f8a1234abcd in some_heavy_loop () from /app/lib.so
#1 0x00007f8a1234cdef in process_frame () from /app/lib.so - 多执行几次(间隔 0.5 秒),如果总停在同一个函数或循环体内(如
for(;;)、while(flag)),基本锁定热点函数 - 注意:若堆栈显示大量
__nanosleep或pthread_cond_wait,反而是正常等待,不是忙循环——此时应查锁或信号量逻辑
用 perf record 定位精确到行的热点代码
当堆栈只显示函数名,但不确定是函数哪一行在“空转”时,perf 是更准的探针:
- 采集 10 秒样本:
perf record -g -psleep 10 - 生成火焰图:
perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > cpu-flame.svg - 打开 SVG 文件,宽度最宽的函数块就是实际消耗最多 CPU 的位置;点击可下钻到具体源码行(需带 debug 符号编译)
- 常见线索:某行
memcpy循环调用、JSON 解析反复 parse、正则表达式回溯爆炸、未加限流的重试逻辑等
检查编译与运行时环境特征
有些“无系统调用的 100%”其实是环境导致的伪忙循环:
- 确认是否启用了
-O2或更高优化等级:某些边界条件下的死循环可能被编译器优化成紧凑跳转,pstack看起来像卡在一行不动 - 检查是否误用了自旋锁(
pthread_spin_lock)且未配超时,尤其在单核虚拟机中极易表现为 100% 用户态且无系统调用 - 查看
/proc/,确认热点函数所在模块是否为 JIT 编译产物(如 Java/Python/C#);这类场景需配合对应语言的 profiler(如/maps jstack+async-profiler)










