系统稳定性—CPU占用过高问题排查及解决
一、概述
在Linux系统与Java应用排查中,CPU利用率和CPU负载是最易混淆的两个指标,也是定位性能瓶颈的关键入口。本文从基础概念切入,拆解“高负载低利用率”“高利用率低负载”“CPU 100%”等典型场景的排查逻辑,并结合实战案例演示从指标异常到代码根因的定位过程。
二、利用率与负载区别
要排查CPU问题,首先需明确“利用率”和“负载”的本质差异——前者反映CPU的“实时工作状态”,后者反映CPU的“任务排队情况”,二者无必然关联,但结合分析可快速定位瓶颈类型。
2.1 CPU时间片与多任务原理
现代操作系统(Linux/Windows/Mac)通过时间片轮转实现 “多任务并发”:
- 单个
CPU核心同一时刻只能执行1个任务; - 操作系统将
CPU时间切分为短时间片(Linux默认5ms-800ms),让多个任务轮流占用CPU; - 用户感知“同时运行”,本质是任务切换速度远超人类反应速度(如1秒切换100次)。
2.2 核心指标定义与计算
| 指标 | 定义 | 计算逻辑 | 核心意义 |
|---|---|---|---|
| CPU利用率 | 单位时间内CPU被任务占用的百分比 (分用户态、系统态) |
(任务占用时间片总和/总时间)×100% | 反映CPU“当前是否繁忙” |
| CPU负载 | 单位时间内正在使用CPU和等待使用CPU的平均任务数(分1/5/15分钟) |
系统级任务队列长度的平均值 | 反映CPU“任务是否排队拥堵” |
关键补充:用户态与系统态利用率
- 用户态
CPU利用率(%us):应用程序代码执行占用的CPU时间(如Java业务逻辑计算); - 系统态
CPU利用率(%sy):操作系统内核调用占用的CPU时间(如文件IO、网络IO、线程切换); - 异常判断:若
%sy持续 > 30%,可能存在IO密集型瓶颈(如频繁磁盘读写)或线程切换过多。
1.3 指标关联与实例对比
通过两个场景理解二者差异:
| 场景 | CPU利用率 | CPU负载(单核) | 本质原因 | 用户感知 |
|---|---|---|---|---|
| 单任务死循环(计算密集) | 100% | ≈1 | 1个任务独占CPU,无排队 | 应用卡顿,其他任务延迟 |
| 10个IO等待任务 | 10% | ≈10 | 10个任务等待 IO(如磁盘读 写),CPU空闲 |
应用响应缓慢,任务堆积 |
负载阈值判断(多核场景)
CPU负载的“合理阈值”与核心数强相关:
-
公式:总核心数 = 物理CPU数 × 每CPU核数;
-
示例:2物理CPU×2核 = 4核心,负载 <4 为正常,>6 需警惕,>8 则严重拥堵;
-
查看核心数命令:
# 物理CPU数
cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l
# 每CPU核数
cat /proc/cpuinfo | grep "cpu cores" | uniq
# 逻辑CPU数(含超线程,可理解为“可用CPU资源数”)
cat /proc/cpuinfo | grep "processor" | wc -l
常用查看命令
# 查看负载(1/5/15分钟平均值)、用户/系统态利用率
uptime # 简洁显示负载:12:34:56 up 100天, 2:30, 1 user, load average: 0.80, 0.95, 1.00
w # 同uptime,额外显示登录用户和进程
top # 实时查看:%us(用户态)、%sy(系统态)、load average(负载)
三、高负载,低利用率
CPU负载很高,利用率却很低,说明处于等待状态的任务很多,负载越高,代表可能很多僵死的进程。通常这种情况是IO密集型的任务,大量任务在请求相同的IO,导致任务队列堆积。
生产环境造成CPU利用率低负载高的具体场景常见的有如下几种。
3.1 场景一:磁盘IO瓶颈(最高频)
进程在cpu上面运行需要访问磁盘文件,这个时候CPU会向内核发起调用文件的请求,让内核去磁盘取文件,这个时候CPU会切换到其他进程或者空闲,这个任务就会转换为不可中断睡眠状态。当这种读写请求过多就会导致不可中断睡眠状态的进程过多,从而导致负载高,CPU低的情况。
3.2 场景二:数据库IO瓶颈
我们都知道MySQL的数据是存储在硬盘中,如果需要进行sql查询,需要先把数据从磁盘加载到内存中。当在数据特别大的时候,如果执行的sql语句没有索引,就会造成扫描表的行数过大导致I/O阻塞,或者是语句中存在死锁,也会造成I/O阻塞,从而导致不可中断睡眠进程过多,导致负载过大。
同样,可以先通过top命令观察,假设发现现在确实是高负载低使用率。

然后,再通过命令ps -aux查看是否存在状态为D的进程,这个状态指的就是不可中断的睡眠状态的进程。处于这个状态的进程无法终止,也无法自行退出,只能通过恢复其依赖的资源或者重启系统来解决。以下图中没有D状态的进程。

Linux上进程的五种状态
- R(task_running):可执行状态,只有在该状态的进程才可能在CPU上运行。而同一时刻可能有多个进程处于可执行状态。
- S(task_interruptible):可中断的睡眠状态,处于这个状态的进程因为等待某某事件的发生(比如等待
socket连接、等待信号量),而被挂起。 - D(task_uninterruptible):不可中断的睡眠状态,进程处于睡眠状态,但是此刻进程是不可中断的。
task_uninterruptible状态存在的意义就在于,内核的某些处理流程是不能被打断的。 - T(task_stopped/task_traced):暂停状态或跟踪状态。
- Z(task_dead - exit_zombie):退出状态,进程成为僵尸进程。进程已终止,但进程描述还在,直到父进程调用
wait4()系统调用后释放。
线上除了上述问题外,还有可能是其他问题,可参考系统稳定性—线上常见问题排查
四、低负载,高利用率
这表示CPU的任务并不多,但是任务执行的时间很长,大概率就是你写的代码本身有问题,通常是计算密集型任务,生成了大量耗时短的计算任务。
怎么排查?直接top命令找到CPU使用率最高的进程,定位到去看看就行了。如果代码没有问题,那么过段时间CPU使用率就会下降的。
4.1 CPU利用率100%排查
步骤1:定位高CPU占用的Java进程
通过top找到CPU占用率高的进程

步骤2:定位进程内高CPU占用的线程
通过top -Hp pid命令查看CPU占比靠前的线程ID

步骤3:线程ID转换为十六进制(jstack需要)
printf "0x%x\n" 74317 # 输出示例:0x1224d(注意前缀0x)
步骤4:通过jstack定位问题代码
jstack 72700 | grep ‘0x1224d’ -C5 --color
注意:jstack的对象是java进程的PID,而不是java线程的PID。
4.2 案例:死循环导致CPU100%
将这段代码上传到linux服务器,并且使用nohup java JvmCpuTest &运行
package com.jvm;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class JvmCpuTest {
private static ExecutorService service = Executors.newFixedThreadPool(5);
private static Object lock = new Object();
public static class TaskTest implements Runnable {
@Override
public void run() {
synchronized (lock){
long sum = 0L;
while(true){
sum +=1;
}
}
}
}
public static void main(String[] args) {
TaskTest taskTest = new TaskTest();
service.execute(taskTest);
}
}
排查结果(线程栈关键信息)
2024-06-08 12:17:36
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.281-b09 mixed mode):
"Attach Listener" #10 daemon prio=9 os_prio=0 tid=0x00007f006c001000 nid=0xc7f waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"DestroyJavaVM" #9 prio=5 os_prio=0 tid=0x00007f00a0009800 nid=0x6955 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"pool-1-thread-1" #8 prio=5 os_prio=0 tid=0x00007f00a00f0000 nid=0x6960 runnable [0x00007f008b0ef000]
java.lang.Thread.State: RUNNABLE
at JvmCpuTest$TaskTest.run(JvmCpuTest.java:14)
- locked <0x00000000f59dfcf0> (a java.lang.Object)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- <0x00000000f59e0ed0> (a java.util.concurrent.ThreadPoolExecutor$Worker)
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f00a00d4800 nid=0x695e runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f00a00b9800 nid=0x695d waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f00a00b6800 nid=0x695c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f00a00b5000 nid=0x695b runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f00a0082000 nid=0x695a in Object.wait() [0x00007f008b6f5000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f5988ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000f5988ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
Locked ownable synchronizers:
- None
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f00a007d800 nid=0x6959 in Object.wait() [0x00007f008b7f6000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000f5986c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000f5986c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Locked ownable synchronizers:
- None
"VM Thread" os_prio=0 tid=0x00007f00a0074000 nid=0x6958 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f00a001e800 nid=0x6956 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f00a0020800 nid=0x6957 runnable
"VM Periodic Task Thread" os_prio=0 tid=0x00007f00a00d7800 nid=0x695f waiting on condition
JNI global references: 5
从下面的信息中就可以看到问题其实是出现在JvmCpuTest.java的14行左右,这里给出的是14行,但是实际情况是14行的附近,结合代码来看一下就很容易问题
"pool-1-thread-1" #8 prio=5 os_prio=0 tid=0x00007f00a00f0000 nid=0x6960 runnable [0x00007f008b0ef000]
java.lang.Thread.State: RUNNABLE
at JvmCpuTest$TaskTest.run(JvmCpuTest.java:14)
- locked <0x00000000f59dfcf0> (a java.lang.Object)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
五、总结
- 先通过“利用率 + 负载”差异,区分
IO密集型(高负载低利用率)与计算密集型(低负载高利用率)瓶颈; - 再针对性选用工具:
Linux命令(top/ps/cat)提取指标,Java工具(jstack)定位代码; - 最终实现从“指标异常”到“代码根因”的精准落地,为
Linux+Java应用CPU问题提供可复用的解决方案。

浙公网安备 33010602011771号