[Java实战]Spring Boot服务CPU 100%问题排查:从定位到解决
Spring Boot服务CPU 100%问题排查:从定位到解决
1. 引言
当Spring Boot服务出现CPU占用率100%时,系统性能会急剧下降,甚至导致服务不可用。本文将通过真实代码案例,详细讲解如何快速定位问题根源,并提供解决方案。无论是死循环、线程阻塞还是资源泄漏,本文帮你一网打尽!
2. 问题现象
- 服务器CPU使用率持续100%
- 接口响应时间变长或超时
- 通过监控工具(如Grafana、Arthas)发现异常线程
3. 排查步骤
3.1 编写测试代码
3.2 运行代码效果
3.3 定位高CPU进程
使用top
命令快速找到占用CPU最高的Java进程:
top -c # 显示完整命令,找到PID
终端输入命令:top -c,找到高cpu进程PID:420364
3.4 定位高CPU线程
通过top -Hp <PID>
查看进程内线程的CPU使用情况,记录线程ID(需转换为十六进制):
#查找进程中的线程
top -Hp <PID>top -Hp 420364# 假设线程ID为433127 → 69be7 输出为16进制printf "%x\n" 433127 [root@localhost java]# printf "%x\n" 433127
69be7
终端输入命令:top -Hp 420364,定位高cpu线程:433127
线程ID转换为16进制:69be7
生成堆栈信息:jstack 420364 > stack.log 生成时PID:420364
通过堆栈信息分析:grep -A 20 ‘nid=0x69be7’ stack.log nid=0x69be7为线程id
根据信息对比原代码可找到问题
3.5分析线程堆栈
使用jstack
捕获线程堆栈并分析:
#生成线程堆栈
jstack <PID> > stack.logjstack 420364 > stack.log# 搜索特定线程
grep -A 20 'nid=0x69be7' stack.log
排查结果及源代码问题对比:类32行运行结果导致cpu 100%
3.5 Arthas 工具定位
以上是根据Java自带的jstack 工具定位, Arthas 工具也能定位排查。定位思路和上面步骤一致
#安装下载启动
curl -O https://arthas.aliyun.com/arthas-boot.jarjava -jar arthas-boot.jar
启动选择需要定位项目:2
直接输入查看高cpu线程: thread
查看堆栈信息找到代码问题: thread 29 (线程ID)
如果想看实时面板信息: dashboard
4. 常见原因与代码示例
4.1 死循环(While循环未正确退出)
问题代码:
@Service
public class CpuProblemController {public void deadLoop() {while (true) { // 无条件退出,导致死循环// 业务逻辑(如未正确更新循环条件)}}
}
解决方案:
@Service
public class CpuProblemController {private volatile boolean running = true; // 添加退出标志public void deadLoop() {while (running) { // 通过控制running变量退出循环// 业务逻辑}}@PreDestroypublic void stop() {running = false; // 服务关闭时触发退出}
}
测试完整类:
package com.example.controller;import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@RestController
@RequestMapping("/pro")
public class CpuProblemController {// 添加退出标志private volatile boolean running = true;// 创建线程池private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());/*** 死循环:模拟 CPU 占满*/@PostMapping("/deadLoop")public void deadLoop() {// 启动多个线程,每个线程执行死循环for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {executorService.submit(() -> {while (running) {// 死循环,模拟高 CPU 使用率System.out.println("=======死循环=========");// 加入计算密集型任务for (int j = 0; j < 1000000; j++) {Math.sin(j); // 计算密集型操作}}});}}@PreDestroypublic void stop() {// 服务关闭时触发退出running = false;executorService.shutdownNow(); // 关闭线程池}
}
4.2 不合理递归(栈溢出与CPU飙升)
问题代码:
public class RecursionController {public int faultyRecursion(int n) {return n + faultyRecursion(n - 1); // 无终止条件 → 无限递归}
}
解决方案:
public class RecursionController {public int safeRecursion(int n) {if (n <= 0) { // 添加递归终止条件return 0;}return n + safeRecursion(n - 1);}
}
4.3 线程池未正确关闭(资源泄漏)
问题代码:
@RestController
public class ThreadController {private ExecutorService executor = Executors.newFixedThreadPool(10);@GetMapping("/task")public String submitTask() {executor.submit(() -> {while (true) { // 线程池任务未终止// 长时间运行的任务}});return "Task submitted!";}// 未重写destroy方法关闭线程池 → 线程泄漏
}
解决方案:
@RestController
public class ThreadController {private ExecutorService executor = Executors.newFixedThreadPool(10);@GetMapping("/task")public String submitTask() {executor.submit(() -> {while (!Thread.currentThread().isInterrupted()) { // 检查中断状态// 可中断的任务逻辑}});return "Task submitted!";}@PreDestroypublic void shutdown() {executor.shutdownNow(); // 服务关闭时终止线程池}
}
4.4 锁竞争(线程阻塞与自旋)
问题代码:
public class LockCompetitionController {private final Object lock = new Object();public void highCpuMethod() {synchronized (lock) {// 长时间持有锁(如复杂计算或IO操作)try {Thread.sleep(10000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}}}
}
解决方案:
public class LockCompetitionDemo {private final Object lock = new Object();public void optimizedMethod() {// 缩小锁范围,只锁必要部分heavyCalculation(); // 将非线程安全操作移到锁外synchronized (lock) {// 仅保护共享资源}}private void heavyCalculation() {// 复杂计算逻辑}
}
4.5 频繁GC(内存泄漏导致CPU飙升)
问题代码:
public class MemoryLeakDemo {private static List<byte[]> cache = new ArrayList<>();@GetMapping("/leak")public void leakMemory() {while (true) {cache.add(new byte[1024 * 1024]); // 不断添加数据 → 触发Full GC}}
}
解决方案:
public class MemoryLeakDemo {private static final int MAX_CACHE_SIZE = 100;private static List<byte[]> cache = new ArrayList<>();@GetMapping("/safe")public void safeMethod() {if (cache.size() >= MAX_CACHE_SIZE) {cache.clear(); // 定期清理缓存}cache.add(new byte[1024 * 1024]);}
}
5. 总结与预防
- 代码审查:重点关注循环、递归、线程池和锁的使用。
- 压测与监控:使用JMeter模拟高并发,通过Arthas实时监控CPU。
- 防御式编程:对递归添加终止条件,对循环添加超时机制。
- 工具辅助:利用
jvisualvm
或async-profiler
进行性能分析。
希望这篇文章对你有所帮助!如果觉得不错,别忘了点赞收藏哦!