系统负载高需区分“真忙”与“假堵”,重点分析负载曲线形态、排查d状态进程/软中断/steal time,并对java应用穿透线程栈定位瓶颈,优先采用cgroup限流而非暴力kill。

系统负载长期偏高,不能只盯着 uptime 里的三个数字。关键要区分是“真忙”还是“假堵”——前者是计算或I/O实在压不过来,后者常因进程卡在D状态、锁竞争或内核调度异常导致负载虚高但CPU空转。
看懂负载曲线的形状比盯数值更重要
用 sar -q 1 60 或 Prometheus 抓取连续一小时的 load average,重点观察三类典型走势:
-
阶梯式缓慢爬升:大概率是线程泄漏(如 Java 应用未关闭 ExecutorService)、连接池持续增长,或日志轮转失败导致写阻塞;配合
ps -eLf | wc -l和cat /proc/$(pidof java)/status | grep Threads验证。 -
周期性尖峰(如每5分钟一次):检查 cron、systemd timer 或应用内部定时任务(如 Spring @Scheduled),用
systemctl list-timers --all和grep -r "0\*/5" /etc/cron.d/快速定位。 -
高位平台震荡(如 12.0 ± 0.3 持续数小时):说明系统进入稳态过载,此时
mpstat -P ALL 1往往显示某几个核长期 99%,而其他核闲置——这是典型的单线程瓶颈或锁串行化,不是整体资源不足。
先排除“非CPU型”高负载
很多负载高但 %us 和 %sy 加起来不到 20%,这时别急着优化代码。重点查三类隐形消耗:
-
D状态进程堆积:执行
ps -axjf | awk '$8 ~ /D/ {print}' | wc -l,若超过 20 个且持续存在,大概率是 NFS 挂载超时、坏盘响应延迟或 SCSI 设备离线;用dmesg -T | tail -30查硬件报错。 -
软中断(si)过高:常见于高并发网络服务(如 Nginx 处理大量短连接),
watch -n 1 'cat /proc/softirqs | head -10'看NET_RX或HI_SOFTIRQ列是否远高于其他项;可尝试开启 RPS/RFS 或调整网卡队列数。 -
不可见的 steal time(仅虚拟机):
top中st值 > 5%,说明宿主机 CPU 被其他虚拟机抢占,需联系云厂商或迁移实例。
Java 类应用的负载归因必须穿透到线程栈
一个 Java 进程占满一个核,往往不是整体慢,而是某个线程陷入高频小循环。标准动作链如下:
- 用
top -H -p $(pgrep -f 'java.*app.jar') -b -n 1 | head -20找出 TID 最高的线程; - 转十六进制:
printf "%x\n" 12345(假设 TID 是 12345); - 抓全量栈:
jstack 6789 > jstack.out 2>&1(6789 是 Java 进程 PID); - 搜索十六进制值:
grep -A 10 -B 5 "0x3039" jstack.out(0x3039 就是 12345 的 hex); - 重点看栈顶三行:若反复出现
Unsafe.park+LockSupport.park,是锁竞争;若停在String.indexOf或正则Matcher.find,是回溯爆炸;若只有Thread.sleep(1)却不休眠,是时钟精度问题导致伪轮询。
应急限流比暴力 kill 更可控
确认是稳态过载后,临时压制比重启更稳妥:
- 对单进程限核(推荐):
cgcreate -g cpu:/lowcpu && echo $PID > /sys/fs/cgroup/cpu/lowcpu/cgroup.procs && echo 30000 > /sys/fs/cgroup/cpu/lowcpu/cpu.cfs_quota_us(限制为 0.3 核); - 避免用
cpulimit,尤其对 JVM:它靠 SIGSTOP/SIGCONT 频繁掐断线程,易触发 GC 线程调度紊乱,引发 Full GC 连锁; - 若必须降优先级,用
renice -n 19 -p $PID,比 kill -9 安全,且不影响已建立的连接和事务状态。










