Java 进程 CPU 飙高排查全流程详解

Java 进程 CPU 飙高排查全流程详解


一、先理解整体思路

当我们发现服务器 CPU 占用率异常高时,需要一步步缩小范围:

复制代码
整台服务器
    └── 找到哪个【进程】占用 CPU 高
            └── 找到该进程中哪个【线程】占用 CPU 高
                    └── 查看该线程正在执行哪段【代码】
                            └── 定位问题根源(死锁 / 死循环)

这就是整个排查流程的核心逻辑:从粗到细,逐层定位


二、第一步:找到占用 CPU 过高的进程

使用 top 命令

在 Linux 终端输入:

bash 复制代码
top

你会看到一个实时刷新的界面,类似下面这样:

复制代码
PID    USER    %CPU   %MEM   COMMAND
12345  root    99.8   2.1    java
6789   root    0.3    0.5    nginx
...

关键字段解释

字段 含义
PID 进程ID,每个进程的唯一编号
%CPU 该进程占用 CPU 的百分比
COMMAND 进程对应的程序名称

此步骤的目标

找到 %CPU 数值异常高的那一行,记录下它的 PID

例如上面例子中,java 进程的 PID 是 12345,CPU 占用 99.8%,这就是我们要重点排查的进程。


三、第二步:找到该进程中占用 CPU 过高的线程

为什么要找线程?

一个 Java 进程内部会同时运行很多线程 (比如处理请求的线程、垃圾回收线程、定时任务线程等)。进程的 CPU 高,一定是因为其内部某个或某几个线程在疯狂消耗 CPU,我们需要精确找到是哪个线程。

使用 top -H -p 进程PID 命令

bash 复制代码
top -H -p 12345

参数解释:

  • -H:以线程为单位展示(默认 top 是以进程为单位)
  • -p 12345:只看 PID 为 12345 的这个进程

执行后你会看到:

复制代码
PID    USER    %CPU   %MEM   COMMAND
12350  root    99.5   2.1    java
12351  root    0.1    2.1    java
12352  root    0.0    2.1    java
...

这里每一行代表的是该 Java 进程内部的一个线程PID 列此时显示的是线程ID

此步骤的目标

找到 %CPU 异常高的那一行,记录下它的线程ID(十进制数字)

例如上面例子中,线程ID 12350 的 CPU 占用高达 99.5%,这就是问题线程。


四、第三步:理解 jstack 命令

jstack 是什么?

jstack 是 Java 自带的一个工具,它能够打印出 Java 进程中所有线程当前的堆栈跟踪信息

所谓堆栈跟踪信息,就是告诉你:某个线程此刻正在执行哪个类的哪个方法的第几行代码

使用方式

bash 复制代码
jstack 12345

执行后会输出大量内容,每个线程对应一段信息,格式大致如下:

复制代码
"线程名称" #线程编号 ...
   java.lang.Thread.State: RUNNABLE
        at com.example.MyService.calculate(MyService.java:88)
        at com.example.MyController.handle(MyController.java:45)
        ...

这段信息告诉我们:

  • 这个线程当前处于什么状态(RUNNABLE 表示正在运行)
  • 它正在执行 MyService.java 第 88 行的 calculate 方法
  • 这个方法是被 MyController.java 第 45 行调用的

五、第四步:解决线程ID进制不一致的问题

问题所在

这是整个流程中一个非常关键的细节:

工具 线程ID的表示方式
top -H -p 十进制 (如 12350
jstack 输出 十六进制 (如 0x302e

两者展示的是同一个线程,但用了不同的进制,所以我们必须把十进制转换成十六进制,才能在 jstack 的输出中找到对应的线程。

为什么会有这种差异?

  • Linux 系统工具(如 top)习惯使用十进制展示线程ID
  • Java 的 jstack 工具在打印线程信息时,使用十六进制展示线程ID(这是 Java 内部的惯例)

两者本质上指的是同一个线程,只是数字的表达形式不同。

如何转换?使用 Linux 命令完成

bash 复制代码
printf "%x\n" 12350
  • printf 是 Linux 的格式化输出命令
  • %x 表示将数字以十六进制格式输出
  • \n 表示换行

执行结果:

复制代码
302e

这样我们就知道,十进制的线程ID 12350,对应十六进制是 302e


六、第五步:用 jstack 精确定位问题线程的代码

使用管道符 |grep 过滤

jstack 输出的内容非常多(一个 Java 进程可能有几十上百个线程),我们不需要全部查看,只需要找到那个十六进制线程ID对应的那段信息。

bash 复制代码
jstack 12345 | grep 302e
  • jstack 12345:打印出进程 12345 的所有线程堆栈信息
  • |:管道符,把前面命令的输出,作为后面命令的输入
  • grep 302e:在输出中搜索包含 302e 的行

执行结果可能如下:

复制代码
"pool-1-thread-1" #25 prio=5 os_prio=0 tid=0x... nid=0x302e runnable

找到这一行后,查看它下面的堆栈信息:

复制代码
"pool-1-thread-1" #25 prio=5 os_prio=0 nid=0x302e runnable
   java.lang.Thread.State: RUNNABLE
        at com.example.OrderService.computeDiscount(OrderService.java:156)
        at com.example.OrderService.processOrder(OrderService.java:89)
        at com.example.OrderController.submitOrder(OrderController.java:34)

这就清楚地告诉我们:问题线程正卡在 OrderService.java 的第 156 行


七、第六步:分析问题根源

两种最常见的导致 CPU 飙高的原因

原因一:死循环

代码中存在一个永远不会结束的循环,线程一直在执行,不断消耗 CPU。

java 复制代码
// 示例:死循环
while (true) {
    // 某个条件判断逻辑写错了,导致永远无法退出循环
    doSomething();
}

特征 :线程状态是 RUNNABLE(正在运行),且每次查看 jstack,该线程都停在同一段代码附近。

原因二:死锁

两个或多个线程互相等待对方释放资源,谁也无法继续执行。

复制代码
线程A 持有锁1,等待锁2
线程B 持有锁2,等待锁1
=> 两个线程永久阻塞,形成死锁

特征 :线程状态是 BLOCKED(阻塞),jstack 甚至会在输出末尾直接提示 Found one Java-level deadlock

如何确认

打开 jstack 定位到的源代码文件,找到对应行号,结合上下文逻辑,判断是否存在:

  • 循环条件永远为真(死循环)
  • 多个锁的获取顺序不一致(死锁)

找到问题代码后,修复逻辑,重新部署,CPU 即可恢复正常。


八、完整流程总结

复制代码
第一步:top
        ↓ 找到 CPU 高的进程 PID(如 12345)

第二步:top -H -p 12345
        ↓ 找到 CPU 高的线程ID(十进制,如 12350)

第三步:printf "%x\n" 12350
        ↓ 转换为十六进制(如 302e)

第四步:jstack 12345 | grep 302e
        ↓ 找到问题线程正在执行的代码位置

第五步:打开源代码,定位到对应行
        ↓ 分析是死循环还是死锁

第六步:修复代码,解决问题

整个排查过程的本质,就是利用操作系统工具和 Java 工具的配合,将 CPU 异常这个宏观现象,一步步精确追溯到具体的一行代码,从而找到并解决问题。

相关推荐
书到用时方恨少!1 小时前
Python NumPy 使用指南:科学计算的基石
开发语言·python·numpy
2501_933329551 小时前
技术深度拆解:Infoseek舆情系统的全链路架构与核心实现
开发语言·人工智能·分布式·架构
赵丙双1 小时前
spring boot 排除自动配置类的方式和原理
java·spring boot·自动配置
8Qi81 小时前
LeetCode热题100--45.跳跃游戏 II
java·算法·leetcode·贪心算法·编程
bilI LESS1 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
Chan162 小时前
MCP 开发实战:Git 信息查询 MCP 服务开发
java·开发语言·spring boot·git·spring·java-ee·intellij-idea
web前端进阶者2 小时前
Rust初学知识点快速记忆
开发语言·后端·rust
lucky九年2 小时前
GO语言模拟C++封装,继承,多态
开发语言·c++·golang
九皇叔叔2 小时前
004-SpringSecurity-Demo 拆分环境
java·springboot3·springsecurity
温天仁2 小时前
西门子PLC编程实践教程:工控工程案例学习
开发语言·学习·自动化·php