CPU 热线程抓到了也处理了,为什么 Load 还是下不来?
高 CPU 热线程已经找到甚至临时处理掉了,系统 Load 却还是居高不下时,问题常常不在“热点线程还在不在”,而在队列积压、等待链、上下文切换、I/O 或容量恢复滞后。先把 CPU、Load、线程状态和资源队列重新放回同一条证据链,再判断下一步该查什么。
线上 CPU 高时,很多排查会先集中在一个非常具体的目标上:
- 找高 CPU 线程
- 抓线程栈
- 识别热点方法
- 临时降级、限流、回滚或修复那条热路径
这条顺序本身没问题。
但真实现场里,非常让人困惑的一幕是:
- 热线程抓到了
- 问题代码也处理了
- 甚至 CPU 已经开始回落
- 可系统 Load 还是高
- 接口 RT 也没有立刻恢复
这时候团队很容易陷入两种相反的判断:
- 一种说,说明刚才的热点线程根本不是根因
- 另一种说,说明还有别的隐藏热线程没抓到
这两种都可能对,但都太快了。
因为 CPU 热线程处理掉之后,Load 不立刻恢复,并不自动说明前面的判断错了。 很多时候,它只是说明:
- 热线程虽然处理了,但积压还没消化
- 线程没在烧 CPU 了,却还在等资源
- 系统层还有上下文切换、I/O、连接队列、下游等待在拖尾
- 负载恢复本来就有滞后
所以这篇文章想解决的问题是:
CPU 热线程已经抓到并处理了,为什么 Load 还是高?这时候下一步到底该查哪里,而不是继续盲抓线程?
如果先记一句话,可以先记这个顺序:
先分清 CPU 是否真的下来了,再判断 Load 高是因为 runnable 还多、I/O 等待高、队列还在积压,还是等待链没有恢复;不要把“热线程已处理”直接等同于“系统已恢复”。
如果 CPU 热线程修了但 Load 还高
| 你现在更像哪种情况 | 更适合先看哪篇 | 为什么 |
|---|---|---|
| CPU 热线程已经抓到并处理了,但 load、RT 或队列迟迟不恢复 | 继续看本文 | 这正是本文的恢复期复核角色 |
| 你还在前半场,仍然需要先抓 CPU 热线程和热点方法 | Java CPU 飙高怎么排查:一套线上定位顺序 | 先把 CPU 主因坐实 |
| 想判断 async-profiler、Arthas、jstack 先用哪个 | async-profiler、Arthas、jstack 到底什么时候该先用哪个? | 那篇更偏工具选择 |
| 你更像是在看系统层工具和 iowait、连接状态、磁盘设备 | top、pidstat、ss、netstat、iostat 分别适合回答什么问题? | 那篇更偏系统层分工 |
先把这篇的边界说清
这篇文章更适合在你已经处理掉前半场热点,但系统还没完全恢复时来看。
它更适合处理后半场问题:前半场的 CPU 热点已经处理后,为什么系统指标和负载没有同步恢复。
一、为什么“CPU 下来了但 Load 还高”最容易让人误判
因为很多人会把 CPU 和 Load 直接理解成同一件事。
但在线上排障里,这两者虽然相关,却不是一回事。
CPU 更像什么
更像:
- 某一段时间里,处理器正在忙什么
- 哪些线程在消耗时间片
- 是业务线程热、GC 线程热,还是系统态忙
Load 更像什么
更像:
- 当前系统上有多少任务在等待获得推进机会
- runnable 线程是不是还很多
- 不可中断等待、I/O 等待、资源等待是不是还在压着系统
所以完全可能出现这种现场:
- 热点线程已经不再明显烧 CPU
- 但系统上仍然有很多线程处于 runnable 或资源等待状态
- 队列和积压还没消掉
- load 仍然高
这也是为什么这类问题不能只继续盯“还有没有热线程”,而要把系统推进能力重新看一遍。
二、第一步先确认:是 CPU 真的回落了,还是你只处理掉了最显眼的一根刺
这一刀非常关键。
先不要急着讨论 load,先回答:
- 实例级 CPU 是否真的明显回落了?
- 高 CPU 线程是否真的不再稳定出现?
- 是不是只是最热的线程消失了,但还有第二梯队线程在顶着?
这一步更建议怎么做
- 再看一轮
top -Hp - 用
pidstat -t看线程趋势是否整体回落 - 对比处理前后 CPU、RT、QPS、线程池 active
为什么这一步重要
因为很多现场并不是“热线程已经处理掉”,而是:
- 最显眼的热点没了
- 但还有一批次热点线程接上来
- 总 CPU 其实并没有实质恢复
如果这一步没先坐实,后面的 load 分析容易建立在错前提上。
三、如果 CPU 真下来了,Load 还是高,最常见先看哪 4 个方向
1. Runnable 线程和上下文切换仍然很多
这类现场很常见。
即使最热的单线程处理掉了,系统也可能还处在:
- 线程数过多
- runnable 线程很多
- 调度仍然很忙
- 吞吐恢复速度赶不上排队压力
典型信号
- CPU 不再特别刺眼
- 但
vmstat的运行队列和 cs 仍然高 pidstat -w看到上下文切换仍然活跃- 线程池 active 很高,但单线程并不特别热
这时问题常常已经从“单点热点”转成“整体调度和并发模型空耗”。
2. I/O 等待还在压着系统
Load 高不一定都是 CPU 方向。
如果你看到:
- CPU user 降了
- 但 iowait 仍高
- 磁盘、日志、落盘、临时文件、本地缓存或数据库客户端所在机器层仍有压力
那 load 高很可能是 I/O 等待在顶着。
这时更该补看的工具
topiostat -x 1pidstat -d -p <pid> 1
这一步很重要,因为不少团队在“热线程已处理”之后,会继续只盯 Java 栈,反而漏掉系统层拖尾。
3. 等待链没恢复,线程还在排队等资源
这是线上非常高频的一类。
例如:
- 数据库连接池 pending 还高
- 慢 SQL 还没恢复
- 下游 RPC 超时还在持续
- Redis、消息队列、对象存储等依赖仍然慢
这时会出现一种很典型的假象:
- 热线程没了
- 但接口还是慢
- load 还是高
因为线程虽然不再烧 CPU,却仍然在系统里等推进。
常见特征
- 大量线程停在 JDBC、HTTP、socket read、Future、连接池获取连接
- queue 不一定很长,但 active 线程不低
- RT 恢复明显滞后于 CPU 恢复
4. 积压还没消化完,系统恢复本来就有滞后
这类经常被误判成“根因还在”。
真实现场里,热点线程处理掉之后,系统不一定立刻回到正常状态,因为还有:
- 线程池 backlog
- MQ backlog
- 请求超时重试遗留波峰
- 缓存回源后的数据库拖尾
- 失败补偿任务还在跑
也就是说,前面那根刺拔掉了,不代表前面刺出来的伤口已经瞬间愈合。
如果你忽略恢复滞后,就很容易误以为:
- 先前判断错了
- 或者还有更深层“神秘根因”
其实很多时候只是系统还在还债。
四、怎么判断现在更像“还在等”,还是“还在忙”
这是下一步最关键的一刀。
1. 更像“还在忙”的信号
- CPU 总体仍然不低
- runnable 线程仍多
- 上下文切换高
- 热点线程虽然换了一批,但仍持续存在
这时你要继续看:
- 是否还有第二层热点路径
- 线程池是否过度并发
- 是否从单线程热点转成整体调度空耗
2. 更像“还在等”的信号
- CPU 已明显回落
- 但 RT、load、active 线程数还高
- 大量线程停在连接池、JDBC、HTTP、Future、网络读写上
- 下游依赖指标恢复明显滞后
这时你更该继续看:
- 数据库
- 下游服务
- 连接池
- 队列积压
- 补偿 / 重试链路
五、一个更稳的排查顺序
如果你已经处理了热线程,但 load 还是高,我更建议按下面顺序继续走。
第 1 步:确认 CPU 是否真的恢复
- 处理前后 CPU 曲线是否明显下移
- 热线程是否不再稳定出现
- 还有没有新的热点线程接力
第 2 步:区分 load 是谁在撑
- runnable 线程多不多
- iowait 高不高
- 上下文切换是否仍很高
这一步可以用:
topvmstatpidstat -wiostat
第 3 步:回到线程状态
- 线程是在跑,还是在等
- 是否大量卡在 JDBC、HTTP、连接池、Future、socket read
- 有没有明显 backlog 未消化
第 4 步:看资源池和依赖链
- 数据库连接池 active / pending
- 下游 RT、timeout、错误率
- MQ backlog
- 缓存回源压力
- 线程池 queue 和 reject
第 5 步:最后才下结论
- 如果仍在忙,就继续找第二层热点或调度空耗
- 如果主要在等,就转向等待链、资源池、积压恢复
- 如果是恢复滞后,就关注积压消化速度和回归曲线,而不是立刻重新开新战场
六、一个典型例子:为什么热线程处理后,load 还是高
假设你处理前看到:
- 某个 JSON 序列化热点线程把 CPU 顶到 95%
- 修复后 CPU 降到 45%
- 但 load 仍然维持在高位
- 接口 RT 只恢复了一部分
继续往下看发现:
- 高峰期间积压了大量请求
- 数据库连接池 pending 仍高
- 一批重试请求在持续回放
- 部分线程还在
Future.get()和 JDBC 获取连接上等待 vmstat显示 runnable 和 cs 仍偏高,但最热单线程已经不明显
这时真正的链路就很清楚:
- 最初是 CPU 热点先把系统拖坏
- 热点处理后,CPU 主因消失了
- 但积压、重试和连接等待开始接管后半场
- load 因为大量等待与调度压力还没恢复
这时候如果继续只找“新的热线程”,就会走偏。
七、关键误判:这类问题最容易在哪些地方走偏
误判 1:Load 没降,就说明热线程不是根因
不一定。
很多时候热线程是第一张多米诺骨牌,后面的 backlog 和等待链需要时间恢复。
误判 2:CPU 下来了,就说明系统应该立刻恢复
错。
CPU 恢复只代表最显眼的计算热点缓解了,不代表连接池、队列、下游和补偿链立刻恢复。
误判 3:load 高就继续只抓线程栈
不够。
你还要看:
- iowait
- runnable 队列
- 上下文切换
- 资源池 pending
- backlog
误判 4:看到等待链,就完全否定之前的 CPU 热点结论
也不对。
很多线上事故就是分阶段演化的:
- 前半段是 CPU 热点
- 后半段是等待链和积压接管
它们可能都是真的,只是处在不同阶段。
八、FAQ:CPU 热线程处理后,load 还是高时最常被问到的几个问题
1. CPU 下来了但 load 不降,是不是系统层问题?
有可能,但不能直接下结论。
还要先分清:
- 是 runnable 线程多
- 是 iowait 高
- 还是等待链和 backlog 在拖尾
2. 什么时候更该先看 vmstat / iostat,而不是继续抓 jstack?
当你已经确认最热线程不明显了,但 load 仍高,这时系统层工具优先级会明显上升。
3. 热线程处理后 RT 没立刻恢复,正常吗?
正常,而且线上很常见。
如果前面已经积压了请求、重试、补偿任务或连接等待,恢复本来就会滞后。
4. CPU、load、RT 三者恢复顺序一定一致吗?
不一定。
很多时候先恢复的是 CPU,后恢复的是 RT 和 load,最后才是队列和错误率。
5. 这类问题下一步最容易漏掉什么?
最容易漏掉:
- backlog
- 连接池 pending
- 下游 RT
- iowait
- 上下文切换
九、CPU 下来了,但现场还没收住时
如果 CPU 热点已经处理掉了,团队却还在追着 load、等待链和积压跑,下面这些文章适合按现场继续往下拆。
这篇放在哪条排查线上
- JVM 与 Java 性能排查
如果你还在补 CPU 这一段
- Java CPU 飙高怎么排查:一套线上定位顺序
- CPU 高和 GC 抖同时出现时,先证明谁是因谁是果?
- top、pidstat、ss、netstat、iostat 分别适合回答什么问题?
- async-profiler、Arthas、jstack 到底什么时候该先用哪个?
如果现场已经转成等待链和系统层拖尾
读到这里,下一步怎么接
- 先把这篇里的主线过一遍,确认 CPU 回落之后,load 还由谁撑着。
- 如果前面的 CPU 热点其实还没坐实,继续看 Java CPU 飙高怎么排查:一套线上定位顺序 和 async-profiler、Arthas、jstack 到底什么时候该先用哪个?。
- 如果 CPU 已经不是主因,线程更多是在等资源,再去看 线程池队列不长但任务还是慢,常见瓶颈在哪里? 和 数据库连接池打满时,根因通常不是连接数太小。
- 如果你怀疑现在已经进入系统层拖尾,再补 top、pidstat、ss、netstat、iostat 分别适合回答什么问题?。
十、最后总结:热线程处理掉,只是前半场结束,不代表系统已经恢复
CPU 热点处理后 load 还是高,这类问题真正容易带偏人的地方,在于它看起来像“前面判断错了”。
但很多真实线上事故并不是一个单点现象,而是一条链:
- 先是热点线程把系统打偏
- 再是积压、等待链和资源池压力接管后半场
- 最后才慢慢恢复
所以更稳的主线应该是:
- 先确认 CPU 是否真的恢复
- 再分清 load 是谁在撑
- 再看线程是在忙、在切,还是在等
- 最后回到 backlog、连接池、下游和系统层拖尾
只要这条顺序不乱,CPU 热线程已经处理但 load 还高 就不会再只是一个让人沮丧的现象,而会慢慢收敛成一条可以解释、可以验证、也可以治理的恢复期故障链。