JAVA项目线上CPU飙升的问题排查

CPU飙升引发的问题:

  1. 系统相应迟钝:CPU占用过高时,系统处理请求的速度会变慢,导致延迟增加
  2. 服务不可用:CPU占用持续过高,可能导致其他任务无法获取足够的处理资源,导致服务宕机
  3. 线程阻塞或者死锁:高CPU占用可能与大量线程阻塞和死锁有关,在多线程任务中,如果线程无法获取CPU资源或者被阻塞,可能导致应用阻塞

常见的引起CPU飙升的原因:

  • 代码出现死循环:在代码中出现死循环会导致CPU飙升
  • 高并发情况导致服务器过载:高并发流量导致服务器压力飙升
  • 线程数量过多:系统创建大量线程,导致CPU负担过重(因为每个线程都会涉及到上下文切换)
  • GC过于频繁:代码中频繁地创建和销毁对象,JVM堆大小不足,导致GC不断运行
  • 代码中存在耗时过长的密集计算型任务:比如加密解密,图片视频处理等

线上排查CPU飙升的流程:

1、首先定位CPU占用进程较高的进程ID

使用top指令进行查看各个进程的资源占有情况,观察出CPU占有较高的进程ID

css 复制代码
top

当我们发现以下类似情况,则说明出现CPU飚升的问题(PID为8820的进程CPU占有率过高)

2、根据进程ID查找导致CPU飙升的线程信息

注:我们案例中是进程8820导致CPU飙升

markdown 复制代码
ps H -eo pid, tid, %cpu | grep 8820
各参数含义
- `ps`:用于查看当前运行的进程状态。
- `H`:显示进程的线程层级(线程树)。`ps` 默认显示进程,但加上 `H` 后,它会显示每个进程的线程(如果该进程有多个线程的话),并以树状结构展示。
- `-eo pid, tid, %cpu`:
    - `pid`:显示进程的 PID(进程标识符)。
    - `tid`:显示线程的 TID(线程标识符),即线程的 ID。
    - `%cpu`:显示该进程或线程使用的 CPU 百分比。
-`| grep 8820`:通过 `grep` 过滤,查找包含 `8820` 的行,通常用于筛选出与特定进程或线程相关的信息。

执行完该指令以后,会出现如下画面:

在图片中展示了PID为8820下的所有线程ID以及他们所占的CPU占有率。 通过观察可以看到:线程ID为8848的线程CPU占有率较高,所以我们后续对8848线程进行分析。

3、查找该线程的堆栈信息

首先将线程ID转换成16进制

perl 复制代码
printf "%x\n" 8848

输出结果会将8848线程id转换成16进制,并输出结果为2290

接着使用jstack指令来获取进程 ID 为 8820 的 Java 程序的所有线程堆栈信息

markdown 复制代码
jstack 8820 | grep -A 10 2290:生成进程ID为8820的所有线程堆栈信息,并且查找2290后面的10行信息并打印
指令参数如下:
- `jstack 8820`:打印出Java程序中ID为8820的进程中所有线程的堆栈信息快照
- `|`:管道符,表示将 `jstack` 命令的输出传递给后续的命令
- `grep -A 10 2290`:查找到2290的后10行记录信息
    - `grep` 进行文本搜索
    - `-A 10` 显示后10行的信息
    - `2290` 搜索要求是2290

通过观察最后显示的进程为8820,线程为2290的堆栈信息情况,我们可以定位到导致CPU飙升的代码所在位置。

4、修改代码逻辑:

定位到原代码出错地点(该代码中的第13行):

kotlin 复制代码
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {

@GetMapping("/cpuTest")
public String cpuTest()

    //死循环
    while (true) {
    }
    
}

我们发现:在代码逻辑中存在死循环,导致产生CPU飙升的问题,我们可以避免死循环。

修改以后的代码逻辑:

kotlin 复制代码
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {

    @GetMapping("/cpuTest")
    public String cpuTest() {
        //避免死循环
        return "cpuLoadTest success";   
    }
    
}

修改后的代码可以解决CPU飙升的问题。

再次测试以后,结果如下:

使用top指令查看以后发现CPU占有率正常。

总结:

解决思路:

  1. 使用top指令,找到CPU飙升的进程id
  2. 使用ps指令,打印出该进程id下的各线程CPU占有率,找到CPU飙升的线程
  3. 使用jstack指令,打印该进程下,指定线程的堆栈信息快照,定位CPU飙升的代码字段
  4. 根据定位,优化逻辑出错的代码。
相关推荐
H5css�海秀1 小时前
今天是自学大模型的第一天(sanjose)
后端·python·node.js·php
SuniaWang1 小时前
《Spring AI + 大模型全栈实战》学习手册系列 · 专题六:《Vue3 前端开发实战:打造企业级 RAG 问答界面》
java·前端·人工智能·spring boot·后端·spring·架构
韩立学长1 小时前
Springboot校园跑腿业务系统0b7amk02(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
sheji34161 小时前
【开题答辩全过程】以 基于springboot的扶贫系统为例,包含答辩的问题和答案
java·spring boot·后端
代码栈上的思考2 小时前
消息队列:内存与磁盘数据中心设计与实现
后端·spring
程序员小假3 小时前
我们来说一下 b+ 树与 b 树的区别
java·后端
Meepo_haha4 小时前
Spring Boot 条件注解:@ConditionalOnProperty 完全解析
java·spring boot·后端
sheji34164 小时前
【开题答辩全过程】以 基于springboot的房屋租赁系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
Victor3565 小时前
MongoDB(57)如何优化MongoDB的查询性能?
后端
Victor3565 小时前
MongoDB(58)如何使用索引优化查询?
后端