把视角降到线程级
整机 CPU 很高但进程列表看不出谁在吃 CPU 时,需要降到线程级:top -H 看全系统线程,ps -L 看指定进程的线程排行,找到热点线程的 TID 后再映射回代码 —— Java 用 jstack 输出的 nid(需把 TID 转成十六进制对齐),Go 用 SIGQUIT 打栈或 pprof 采 CPU profile。线程名常常大量重复,因此定位必须靠 TID 与栈工具交叉,而不能靠名字。
基本信息
| 适用平台 | Linux(procps-ng 的 top/ps/pidstat)配合语言侧工具 |
|---|---|
| 线程排行 | top -H -b -n1、ps -L -p PID --sort=-pcpu |
| Java 对应 | jstack / jcmd,TID 转十六进制与 nid 对齐 |
| Go 对应 | kill -QUIT 打 goroutine 栈,或 pprof 采 profile |
| 权限要求 | 同用户进程一般可查,跨用户需 root |
典型使用场景
- 多线程服务 CPU 高,需要定位到具体线程
- 线程名重复无法靠名字区分
- 瞬时热点需要采样而不是单点快照
- Java 应用线程死循环或锁竞争
- Go 服务 goroutine 泄漏或热点函数定位
线程排行与语言侧对应
线程排行与语言侧对应
top -H -b -n1 | head -20 # 全系统线程视图,按 CPU 排序
ps -L -p 1234 -o pid,tid,pcpu,stat,comm --sort=-pcpu | head # 指定进程的线程排行
pidstat -t -p 1234 1 3 # 按线程采样,观察短时热点
# Java:TID 转十六进制后与 jstack 的 nid 对齐
printf '%x\n' 12345
jstack 1234 | grep -A20 'nid=0x3039'
# Go:SIGQUIT 打全量 goroutine 栈,或采 CPU profile
kill -QUIT 1234
curl -s http://127.0.0.1:6060/debug/pprof/profile?seconds=30 > cpu.pprof
go tool pprof -top cpu.pprof工具与对应关系
| 工具/命令 | 作用 | 注意 |
|---|---|---|
| top -H | 全系统按线程显示 | 先找到吃 CPU 的 TID |
| ps -L -p | 指定进程的线程列表 | 与 top 交叉确认 |
| pidstat -t | 线程级采样 | 比单次快照更适合抓波动 |
| jstack + nid | Java 线程栈 | TID 必须转十六进制才能对齐 |
| SIGQUIT / pprof | Go 栈与 CPU 采样 | SIGQUIT 把栈打到 stderr,注意日志落点 |
| perf | 函数级热点 | 需符号才能读,能定位到函数而非线程 |
单次快照容易错过瞬时热点:采样式工具(pprof、perf、pidstat)给出的是一段时间内的分布,更适合抓间歇性问题。
在生产进程上执行 jstack、kill -QUIT 会带来短暂停顿并产生大量输出:避开业务高峰,先确认输出不会写满磁盘或淹没日志。
推荐的定位顺序:先用 top -H 找到吃 CPU 的 TID,再用 ps -L 交叉确认,最后才用语言侧的栈工具细看,这样一次就能收敛到目标线程,减少生产环境上的侵入式操作。
线程只是入口,还要看函数:perf 或 pprof 能把热点收敛到具体函数甚至代码行,定位到线程之后通常还要再走这一步。
注意 D 状态:多线程程序里 D 表示不可中断睡眠(多为 IO 等待)。线程多、CPU 不高但 D 状态集中时,瓶颈在磁盘或网络,继续在线程层面找 CPU 会南辕北辙。