一文搞懂CPU使用率飙升至100%的原因及解决方案
前言
作为一名 Java 后端程序员,经常会面临各种各样的线上问题,能够快速、准确地定位并解决问题,是迈向中高级开发的必经之路。今天我们就来聊一下CPU使用率飙升至100%的问题,这种情况不仅会导致系统响应变慢,甚至可能引发服务不可用。本文将结合实际案例,详细分析CPU使用率飙升的原因,并提供相应的排查和解决方案。
一、CPU使用率为什么会飙升?
- 无限循环或高频计算:这是最常见的原因之一。某些代码逻辑可能因为错误或设计缺陷,陷入无限循环或执行高频计算任务,导致线程持续占用CPU。
- 锁争用:当多个线程竞争同一资源时,可能会导致锁争用。线程反复尝试获取锁,却无法成功,从而产生大量CPU消耗。
- 垃圾回收(GC):如果JVM的堆内存设置不合理,可能会导致垃圾回收线程频繁工作。频繁的GC不仅会占用大量CPU资源,还可能导致系统响应变慢。
二、CPU使用率飙升的排查步骤
0. 测试CPU飙升的代码
类名:ProjectController
**这个代码中,while (true) 导致了一个无限循环,线程会持续计算平方根,从而占用大量CPU资源**
@GetMapping("/testCpu")
public void testCpu() {
while (true) {
double result = 0;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
result += Math.sqrt(i);
}
}
}
1. 确认占用CPU最高的进程
当发现CPU使用率飙升时,首先需要确认是哪个进程导致的。可以使用top命令查看系统中CPU占用最高的进程。
top命令的输出显示了系统的实时性能状况,包括CPU、内存、交换空间使用情况以及各个进程的资源占用情况。
执行命令:top
输出以下2部分内容:
第一部分:系统的总体状态,包括CPU、内存、交换空间的使用情况,以及进程数量和负载。
top - 23:02:58 up 222 days, 6:48, 1 user, load average: 0.64, 0.19, 0.10
Tasks: 173 total, 2 running, 171 sleeping, 0 stopped, 0 zombie
%Cpu(s): 50.1 us, 0.0 sy, 0.0 ni, 49.8 id, 0.0 wa, 0.0 hi, 0.2 si, 0.0 st
KiB Mem : 3861292 total, 1966080 free, 1289584 used, 605628 buff/cache
KiB Swap: 1679356 total, 1617148 free, 62208 used. 2155704 avail Mem
第二部分:各个进程的详细信息,包括CPU占用、内存占用等。可以看到最占用资源的进程是PID为17329的Java进程,CPU占用率高达100.3%。
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17329 usr 20 0 4894520 1.0g 13972 S 100.3 26.6 2:43.30 java
17942 usr 20 0 162108 2332 1580 R 0.3 0.1 0:00.07 top
1 root 20 0 191204 3428 2048 S 0.0 0.1 4:15.67 systemd
第一部分关键参数详解:
%Cpu(s): 50.1 us, 0.0 sy, 0.0 ni, 49.8 id, 0.0 wa, 0.0 hi, 0.2 si, 0.0 st
这些数值表示CPU的使用情况:
us:50.1%的CPU时间被用户空间的进程(用户程序)占用
sy:0.0%的CPU时间被内核空间的进程占用
ni:0.0%的CPU时间被改变优先级的进程占用(优先级调整)
id:49.8%的CPU时间处于空闲状态
wa:0.0%的CPU时间等待I/O操作
hi:0.0%的CPU时间用于硬件中断
si:0.2%的CPU时间用于软件中断
st:0.0%的CPU时间被虚拟化环境占用(如果是虚拟机中运行的话)
KiB Mem : 3861292 total, 1966080 free, 1289584 used, 605628 buff/cache
3861292 total:总内存
1966080 free:剩余空闲内存
1289584 used:已使用的内存
605628 buff/cache:用于缓存和缓冲区的内存
第二部分关键参数详解:
1. PID:进程ID。每个进程都有一个唯一的PID,用来标识它。
2. USER:进程的所属用户。
3. %CPU:进程占用的CPU百分比。(100.3:Java进程占用了100.3%的CPU资源)
4. %MEM:进程占用的内存百分比。(26.6:Java进程占用了系统内存的26.6%)
5. TIME+:进程使用的CPU时间。(2:43.30:Java进程使用了2分钟43秒30毫秒的CPU时间)
6. COMMAND:进程的命令名称或可执行文件名。(java:表示进程是由Java应用程序启动的,top:表示这个进程是top命令本身)
2. 找到进程下占用CPU最高的线程
确认是Java应用导致CPU占用过高后,需要进一步了解是哪个线程导致的。可以使用top -Hp命令查看进程下每个线程的CPU使用情况:
执行命令:top -Hp 17329
注:-H 参数会显示该进程中每个线程的 CPU 使用情况。
17329就是步骤1的输出结果中第一行的进程id。
会输出以下内容:
top - 23:03:16 up 222 days, 6:49, 1 user, load average: 0.74, 0.25, 0.12
Tasks: 174 total, 1 running, 173 sleeping, 0 stopped, 0 zombie
top - 23:04:09 up 222 days, 6:49, 1 user, load average: 0.90, 0.37, 0.17
Threads: 98 total, 1 running, 97 sleeping, 0 stopped, 0 zombie
%Cpu(s): 50.2 us, 0.0 sy, 0.0 ni, 49.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 3861292 total, 1965988 free, 1289672 used, 605632 buff/cache
KiB Swap: 1679356 total, 1617148 free, 62208 used. 2155616 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17419 usr 20 0 4894520 1.0g 13972 R 99.7 26.6 2:05.90 http-nio-9526-e
17346 usr 20 0 4894520 1.0g 13972 S 0.3 26.6 0:00.45 mysql-cj-abando
17329 usr 20 0 4894520 1.0g 13972 S 0.0 26.6 0:00.00 java
17330 usr 20 0 4894520 1.0g 13972 S 0.0 26.6 0:17.01 java
17331 usr 20 0 4894520 1.0g 13972 S 0.0 26.6 0:01.06 java
17332 usr 20 0 4894520 1.0g 13972 S 0.0 26.6 0:01.15 java
可以看到,占用CPU最高的是线程id为17419的线程。
3. 将线程id转换为16进制
Java 的 jstack 工具输出的线程 ID 是十六进制格式,而 top 中显示的是十进制 ID。因此需要将线程 ID 转换为十六进制格式,以便后续查看线程堆栈信息。
执行命令:printf "%x\n" 17419
输出以下内容:
440b
注:该命令会输出对应的十六进制线程 ID,其中 17419 是 top 中显示的高 CPU 占用的线程 ID。
4. 打印Java 应用的线程堆栈信息
使用jstack工具获取Java进程的线程堆栈信息,找出导致高CPU占用的线程及其运行的代码。
执行命令:jstack 17329 | grep "440b" -A10 --color
输出以下内容:
"http-nio-9526-exec-1" #90 daemon prio=5 os_prio=0 tid=0x00007f4684cac800 nid=0x440b runnable [0x00007f46240ac000]
java.lang.Thread.State: RUNNABLE
at com.test.controller.ProjectController.testCpu(ProjectController.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
注:该命令会显示该线程的堆栈信息,-A 10 显示十六进制 ID 匹配行之后的 10 行。
5. 分析线程堆栈
通过 jstack 的输出,你可以看到占用 CPU 的线程在执行什么操作。
根据步骤4的输出结果,可以看到第一行就打印出了问题代码:正好是我们测试CPU飙升的代码。
at com.test.controller.ProjectController.testCpu(ProjectController.java:45)
6. 解决方案
因为代码是由于高频计算导致CPU占用过高,需要优化代码。例如,可以为循环添加合理的退出条件,或者优化计算逻辑。
总结
CPU使用率飙升至100%是一个常见的问题,但通过合理的排查和优化,可以有效解决。本文介绍了CPU使用率飙升的常见原因、排查方法以及解决方案。希望本文能帮助你在遇到类似问题时,快速定位并解决。
如果你在实际工作中遇到其他问题,欢迎在评论区留言,我们一起探讨!