凌晨2点,告警群炸了。某Java服务CPU使用率从10%飙到800%,整台机器负载冲到30。我第一反应是打开终端,top,然后top -Hp找线程,printf '%x\n'转十六进制,jstack抓栈。这套动作我做过上百次,肌肉记忆。但那天我多看了一眼vmstat 1,发现us(用户态)只占30%,sy(系统态)占了60%,还有大量si(软中断)。这个数字让我把已经敲到一半的jstack命令删了。直觉流直接进JVM,数据流先看操作系统。两种路径,结果差了4个小时。
先说说直觉流为什么容易翻车。大多数人排查CPU高,第一反应就是“哪个线程在跑”。命令没错:top -Hp pid,按H,找到高CPU线程,printf '%x\n' tid,然后jstack pid | grep -A 50 nid=0x...。如果线程栈显示是GC线程,就调GC参数;如果是业务线程,就看代码。但这次,高CPU线程列表里,很多线程的nid在jstack里根本找不到,或者显示为“Attach Listener”之类的。而且用户态CPU不高,系统态很高,说明CPU时间花在内核。我换到数据流:先mpstat -P ALL 1,发现每个核的%soft都很高,达到40%以上。再看/proc/softirqs,NET_RX软中断数量暴涨。用sar -n DEV 1看网卡,eth0的RX包量巨大,但带宽不高,说明是小包风暴。最终定位到某个服务在疯狂发UDP小包做健康检查,频率从每秒10次改成了每秒1000次。调整后CPU降回15%。
这次经历让我重新思考排查顺序。直觉流适合“应用层问题”,比如死循环、锁竞争、频繁GC。数据流适合“系统层问题”,比如软中断、中断亲和性、磁盘IO。很多人一上来就jstack,是因为培训里都这么教。但如果你发现jstack里没有明显热点,或者CPU的sy/si占比高,就该换方向。我的独立观点:排查CPU问题,先看“CPU时间花在哪”,而不是“哪个线程在跑”。用pidstat -u -t 1看每个线程的用户态/系统态,比top更细。还有一个被忽视的工具:perf top,能直接看到内核函数的热点。那次如果早点用perf top,5分钟就能定位到net_rx_action。
最后给一个可复现的排查清单。第一步:uptime看负载,top看整体,vmstat 1看r/b/si/cs。第二步:mpstat -P ALL 1看每个核的soft/steal。第三步:pidstat -u -t -p pid 1看线程级。第四步:如果是用户态高,再jstack;如果是系统态高,用perf top -p pid或cat /proc/softirqs。第五步:对比历史数据。我那次还用了dstat --top-softirqs,直接显示NET_RX排名第一。最后发现是某微服务的健康检查间隔配置错误,从30秒写成了30毫秒。改完重启,CPU从800%掉到12%。全文没有金句,只有踩过的坑。