pidstat -w -t 1 是查线程级上下文切换的唯一轻量方案,输出每线程每秒的cswch/s和nvcswch/s,需加-t才显示线程而非进程汇总,首行是累计值,第二行起为真实速率。

pidstat -w -t 1 能看到线程级切换,但默认不显示空闲线程
想查具体某个线程(LWP)的上下文切换次数,pidstat -w -t 1 是唯一轻量、可落地的方案。它输出的每行对应一个线程(不是进程),字段仍是 cswch/s 和 nvcswch/s,但单位是“该线程每秒切换次数”。
常见错误是漏掉 -t:不加这个参数,pidstat -w 只显示进程汇总值,Java 应用里几十个线程全被揉成一个数字,根本看不出哪个线程在疯狂切。
-
pidstat -w -t 1:每秒刷新,只显示cswch/s或nvcswch/s非零的活跃线程 -
pidstat -w -t -p 1234 0.5:盯住 PID 1234 的所有线程,半秒一采,适合抓 GC 线程或锁竞争瞬间 - 首次输出是累计值,第二行起才是真实速率——别拿第一行数据当结论
- 老版本 sysstat(如 RHEL 7 自带的 10.1.5)不支持
-w和-t同时生效,先跑pidstat -V确认版本 ≥ 11.0.0
Java 应用里线程名看不见?得结合 jstack 和 LWP ID 对齐
pidstat -w -t 输出的 TID 列是十进制线程 ID(即 LWP ID),而 jstack 1234 打印的线程 ID 是十六进制且带 0x 前缀。两者必须手动换算才能对应上。
比如 pidstat 显示某行 TID=29845,你在 jstack 输出里搜 0x7495(29845 的十六进制),就能定位到具体线程名和栈状态。
-
printf "%x\n" 29845快速转十六进制 - 重点关注
java.lang.Thread.State: BLOCKED对应高cswch/s;RUNNABLE但%CPU很低,可能卡在 safepoint 或 native 方法里 - 如果
nvcswch/s高但线程状态是WAITING,大概率是锁粒度太细,或线程数远超 CPU 核数
/proc/[pid]/task/[tid]/status 里能查单次切换快照,但没法实时看速率
每个线程在 /proc/[pid]/task/[tid]/status 中有 voluntary_ctxt_switches 和 nonvoluntary_ctxt_switches 两个字段,是自线程启动以来的累计值。它不提供每秒速率,但能排除瞬时毛刺干扰。
例如你怀疑某个线程在启动后 10 分钟内切了 200 万次,就直接读这个文件,比靠 pidstat 持续采样更稳。
- 命令示例:
grep -E 'voluntary|nonvoluntary' /proc/1234/task/29845/status - 两次读取差值 ÷ 时间间隔 = 平均 cps,适合验证长期趋势
- 注意:该路径下
[tid]是十进制,和pidstat -t的TID一致,不用转换 - 权限问题常见:非 root 用户可能读不到其他用户进程的 task 目录
perf record -e sched:sched_switch 能抓切换事件,但开销大、难过滤
真要深挖“谁切了谁、为什么切”,perf record -e sched:sched_switch 是唯一能拿到原始调度事件的手段,但它记录的是每次切换的完整上下文(prev_pid → next_pid、CPU、时间戳),数据量极大。
它不适合日常巡检,只用于复现明确问题后的根因分析。随便跑 5 秒就可能生成百 MB trace 文件,解析也麻烦。
- 最小可用命令:
sudo perf record -e sched:sched_switch -a sleep 5 - 过滤特定进程:
sudo perf script | awk '$3 ~ /1234/ {print}'(假设 1234 是目标 PID) - 别用
perf top看这个事件——它只聚合,丢掉了 prev/next 关键信息 - 切换频繁时,
perf本身会成为性能瓶颈,cs值可能被拉高,形成干扰
pidstat -w 就够了,却忘了加 -t;或者看到线程切换高,没意识到得用 jstack 把 TID 和线程名对上。这两步跳过,后面所有分析都是盲人摸象。











