服务器 CPU 持续接近 100% 时如何排查?
未标记
考查重点:CPU 热点与请求归因。先确认流量和指标口径,再用进程/线程 CPU 定位热点,关联栈与请求;有影响先止损,修改后复测占用和延迟
本题目录
参考答案
核心回答
CPU 持续接近 100% 时,我会按确认影响与指标口径 → 定位进程、线程 → 关联调用栈和业务请求 → 验证根因并恢复服务来排查。
先看接口成功率、延迟和积压,确认是整机、容器还是单个进程的 CPU 高,并对照流量、发布、配置和定时任务。若服务已明显受损,先按实际情况限流、摘除异常实例或回滚,同时尽量保留现场。随后用 top、pidstat 等找出持续消耗 CPU 的进程和线程,再结合运行时栈、CPU 采样、GC 与调用链判断是在正常计算、死循环、自旋、频繁分配回收,还是内核与系统资源问题。
定位不能停在“某进程很高”或“栈里有个 RUNNABLE 线程”。需要把同一时间窗口内的 CPU 消耗、热点代码与触发请求对应起来,修复后再比较吞吐、延迟、错误、队列和单位请求开销,确认业务恢复且没有把瓶颈转移到下游。
先确认“100%”代表什么
首先确认告警实例、采样时间、持续时长,以及监控的分母。Linux top 的进程 CPU 在常见 Irix 模式下,约 100% 可以表示占用了一个逻辑 CPU;多线程进程可能超过 100%。整机汇总、Solaris 归一化模式和容器监控可能使用不同口径。
因此,一个进程显示 100%,并不必然意味着整台多核服务器都已打满。还要看每核分布、实际可用 CPU,以及容器的 CPU 限额和节流情况:宿主机还有空闲,容器也可能已经用完自己的配额。
同时把 CPU 使用率与 Load Average 分开。Linux 的 Load 包含可运行任务和不可中断睡眠任务;I/O 等待堆积时,Load 可能很高,CPU 却并不忙。不能看到 Load 高就直接认定死循环。
从系统层缩小范围
下面以 Linux 为例,先观察总量、各核和时间变化:
top
vmstat 1 5
mpstat -P ALL 1 5
top 中可以按 P 排序、按 1 查看各核。mpstat、pidstat、iostat 通常由 sysstat 提供,线上环境未必都已安装;先使用现有可用的诊断工具。
重点辨别几条路径:
- 用户态计算高:先找业务进程和热点线程,检查计算、序列化、压缩、正则、重试或循环。
- 内核态、软中断或上下文切换异常:结合系统调用、网络包量、线程活动、内存回收及内核热点继续分析,不能只看业务方法。
- I/O 等待高、D 状态任务多:检查磁盘或网络存储、设备队列与延迟,必要时用
iostat -xz 1 5;等待 I/O 与 CPU 正在执行计算是不同现象。 - 虚拟化等待或容器配额受限:核对
steal、CPU 分配、配额和节流指标,再判断是否是可获得的算力不足。
这些现象都需要与历史基线和业务时段比较。单个字段超过某个固定数值,不足以直接确定原因。
从进程找到真正的热线程
确认高占用进程后,记录它的 PID、启动时间、所属服务或容器和版本,避免把日志采集、定时任务、数据库或其他同机进程误当成业务应用。
例如 PID 为 12345,可以进一步观察线程:
top -H -p 12345
pidstat -u -t -p 12345 1 5
这里的 PID 是示例,实际使用时替换成目标进程。观察多个采样区间,区分一个线程持续占满、多个线程共同繁忙,还是线程快速创建退出。pidstat 的区间报告比只看进程启动以来累计的 CPU 时间更适合定位当前突增。
如果热点总在同一个线程,优先关联该线程的栈;如果大量线程共同繁忙,再看流量放大、线程池并发、分配回收和任务调度。进程 CPU 高也可能是正常业务量增长,不能因为它繁忙就直接终止。
Java 服务怎样关联到代码
对于 Linux 上 HotSpot 的平台线程,可以把操作系统线程 ID 与线程转储里的原生线程 ID nid 对应。假设热线程十进制 ID 是 23456:
printf '%x\n' 23456
jcmd 12345 Thread.print -l > thread-dump-1.txt
转换结果为 5ba0,在线程转储中查找对应的 nid=0x5ba0,查看下面的方法栈。进程和线程 ID 都应来自同一 PID 命名空间,容器内外的编号不能直接混用。诊断工具也要使用与目标 JVM 适配的版本和运行身份。
根据现场负载间隔取得少量线程转储,与 CPU 采样时间对应:
- 热线程多次停在同一业务循环、正则或转换逻辑,检查退出条件、输入规模与算法。
GC线程占用明显,结合GC日志、分配速率、回收前后内存与停顿,区分大量临时对象、内存压力等原因。- 编译线程繁忙,结合启动、预热和代码生成判断
JIT活动是否符合预期。 - 多线程自旋或同步开销高,结合锁持有者、竞争与上下文切换分析;大量线程阻塞不等于它们都在消耗 CPU。
Java 的 RUNNABLE 只是一种线程状态,可能包含原生调用中的等待,不能单凭它认定热点。一份线程栈也只是瞬时位置。若持续栈不足以解释 CPU 去向,再选择短时、受控的 JFR、CPU profiler 或 perf 采样,按运行时与符号支持情况分析调用栈;采样比例也不能直接当作接口响应时间占比。
线程转储本身有开销,Oracle JDK 25 文档将 Thread.print 标为中等影响,具体与线程数有关。CPU 已经饱和时,应限制采集范围和频率;没有内存证据时,不必一开始就抓大体积 Heap Dump。
怎样把热点变成根因结论
热点代码需要继续追到触发条件。例如:
JSON序列化繁忙:是哪类请求突然返回大量数据,还是正常请求量整体增加?- 重试循环繁忙:下游异常后是否缺少退避、退出条件或总重试预算?
GC占用升高:是某次发布增加临时对象,还是批处理输入扩大、缓存无界增长?- 系统态或网络开销升高:是否存在异常连接、日志风暴、频繁线程切换或页面回收?
把异常前后的请求量、数据大小、发布和定时任务,与线程或 CPU 采样、日志、Trace 及依赖指标对照。一个合理结论应能解释“为什么从这个时间开始、为什么这些实例受影响、为什么这段代码持续消耗 CPU”。
原面经也出现了慢 SQL 的追问。应先确认高 CPU 在应用服务器还是数据库服务器:数据库全表扫描、排序等可能消耗数据库 CPU;应用等待慢 SQL 则往往表现为等待和堆积。只有重试放大、结果处理等额外机制存在时,才可能进一步推高应用 CPU,不能把所有 CPU 告警都归因于慢 SQL。
恢复和修复后看什么
恢复措施根据证据选择:异常发布可回滚,非核心重任务可暂停或隔离,突增流量可限流或扩容,缺陷则修正循环、算法、重试、批次或对象分配。扩容前也要确认下游能够承接更多请求。
在可比较的流量和数据条件下复核 CPU、每请求开销、成功率、尾部延迟、积压及依赖负载。CPU 降下来了但任务不再处理,或流量被转移到别处,不算完成修复。将触发输入加入回归,并补齐当时缺失的监控或取证能力。
常见追问
重启后好了,能确认根因吗?
重启可以恢复部分现场状态,但不能单独区分死循环、内存压力、连接异常或流量问题。需要结合重启前证据、可重现触发条件和修复后的对照判断。
为什么不直接把线程池调大?
如果已经是 CPU 计算饱和,增加线程通常只会增加竞争和切换;如果主要在等待,又要检查下游容量和并发边界。线程数应由任务特征和资源情况决定。
不是 Java 服务怎么办?
系统与进程层的排查顺序相同,运行时层使用相应的栈和 CPU profiler。Python、Go、原生程序的线程、协程与操作系统线程映射不同,不能照搬 HotSpot 的 nid 查找方式。
参考资料
- 国内:JavaGuide:Java 后端线上问题排查:参考告警确认、止血留证和 CPU 线程关联的排查思路,组织正文中由监控、线程栈到触发请求的根因证据链。
- 国内:JavaGuide:CPU 调度与系统负载详解:借鉴 CPU 使用率、Load、等待和调度的解释,区分正文中的计算、排队与 I/O 等待,并说明对应的观察指标。
- 国内:阿里云:Linux 实例 CPU 使用率或负载较高问题:借鉴业务进程、I/O、内核和中断的分支排查,组织正文中由系统现象选择后续定位方向的步骤。
- 国内:腾讯云:节点高负载排障处理:采用 CPU 空闲但负载升高的 I/O 案例,解释正文中高 Load 为什么不能直接等同于 CPU 计算热点。
- 国外:procps-ng:top 手册:依据线程模式、PID 过滤与 Irix/Solaris 百分比定义,解释正文中进程 100% 与整机 CPU 使用率的关系。
- 国外:Oracle JDK 25:jcmd:依据目标进程、运行身份与 Thread.print 的参数和影响说明,补充正文取得 JVM 线程栈的方法及采集开销条件。


