Java 线上问题排查(尤其是高 CPU 问题)的标准流

top 命令:发现 Java 进程 CPU 占用 100%。

ps -ef | grep java 或 jps 找到 Java 进程 ID。

top -Hp <pid>:查看该进程中哪些线程占用 CPU 高。

printf "%x\n" <thread_id>:将线程 ID 转为 16 进制。

jstack <pid> | grep -A 20 <nid>:导出线程栈,定位到高 CPU 的线程正在执行什么代码。

以上几个命令是 Java 线上问题排查(尤其是高 CPU 问题)的标准流程,也是面试中经常被问到的“如何排查 CPU 100% 的问题”的核心操作步骤。

下面我将 逐条详细解释每个命令的作用、使用方式、输出含义,以及它们是如何串联起来定位问题的,帮你不仅会用,还能在面试中清晰、专业地表达出来。


一、整体排查思路回顾(先有个大局观)

当线上 Java 服务出现 CPU 使用率飙高(比如 100%),我们想知道:

是哪个进程?哪个线程?在跑什么代码?为什么会一直占用 CPU?

那么典型的排查路径是:

  1. 找到高 CPU 的 Java 进程
  2. 找到该进程中哪个线程占用 CPU 高
  3. 把线程号转为 16 进制(因为 jstack 中的线程 ID 是 16 进制的)
  4. 用 jstack 导出线程栈,找到对应线程正在执行的代码

下面我们逐步详解每一步。


二、步骤 1:top 命令 → 发现 Java 进程 CPU 占用 100%

🔧 命令:

top

📌 作用:

top 是 Linux 下最常用的 实时系统监控工具,可以看到:

  • 整个机器的 CPU、内存使用情况
  • 每个 进程的 CPU 占用率、内存占用、运行时间、进程ID(PID)等

🧠 重点关注:

  • %CPU 列:看哪个进程的 CPU 使用率特别高,比如接近 100%
  • PID 列:记下那个高 CPU 进程的进程号(就是 Java 进程的 pid)
  • 一般 Java 应用会显示进程名如 java 或者你打包的名称(比如 your-app.jar

🖥️ 示例输出(简化):

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
12345 appuser   20   0 10.1g   4.2g  12345 S  99.9  5.6   10:20.12 java

👉 这里 PID=12345java 进程 CPU 使用率是 99.9%,基本就是它了!


三、步骤 2:ps -ef | grep javajps → 找到 Java 进程 ID

这一步其实和 top 可能有点重叠,因为 top 已经能让你看到 Java 进程 PID 了,但有时你可能需要 进一步确认是哪个 Java 应用,或者你忘记了刚才 top 里的 PID,那可以用:

方式一:ps -ef | grep java

🔧 命令:

ps -ef | grep java

📌 作用:

列出所有含有 java 的进程,包括启动命令、参数等,适合查看具体是哪个 Java 应用在运行。

🖥️ 示例输出:

appuser  12345  1  5 10:00 ?        00:10:20 /usr/bin/java -jar your-app.jar
appuser  12346  1  0 10:01 ?        00:00:01 /usr/bin/java -jar another-app.jar

👉 找到那个你怀疑的 Java 进程,比如 PID = 12345


方式二:jps(更轻量,只列出 Java 进程)

🔧 命令:

jps -l

📌 作用:

jps(Java Virtual Machine Process Status Tool)是 JDK 自带工具,只列出当前用户下的 Java 进程及其主类或 jar 包名,更加清晰明了。

🖥️ 示例输出:

12345 your-app.jar
12346 another-tool.jar

👉 很直观,能看到 PID 12345 运行的是 your-app.jar,基本可以确定问题进程。


四、步骤 3:top -Hp <pid> → 查看该进程中的高 CPU 线程

你已经知道是哪个 Java 进程(比如 PID=12345)CPU 特别高,但一个 Java 进程里往往有多个线程,其中可能只有一个或几个线程在疯狂占用 CPU

🔧 命令:

top -Hp 12345

注意:把 12345 替换成你实际的 Java 进程 PID。

📌 作用:

top -Hp查看某个进程中各个线程的 CPU 使用情况,可以精确到线程级别。

🧠 重点关注:

  • PID:仍然是进程号,这里是你刚才的大 PID(比如 12345)
  • SPID(或 TID)线程号(Thread ID),这是我们要找的!
  • %CPU:看哪个线程占用 CPU 最高

🖥️ 示例输出(简化):

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ THREAD
12345 appuser   20   0 10.1g   4.2g  12345 R  45.0  5.6   00:10.20 12346
12345 appuser   20   0 10.1g   4.2g  12345 R  35.0  5.6   00:09.45 12347
12345 appuser   20   0 10.1g   4.2g  12345 S  10.0  5.6   00:00.10 12348

👉 假设线程号为 12346 的线程占用了 45% 的 CPU,它可能就是罪魁祸首!

记住这个线程号(比如 12346),下一步要把它转成 16 进制!


五、步骤 4:printf "%x\n" <thread_id> → 将线程号转为 16 进制

Java 的线程栈(比如通过 jstack 查看的)里,线程 ID(nid)是用 16 进制表示的,而 top -Hp 给出的是 10 进制的线程号,所以我们需要转换。

🔧 命令:

printf "%x\n" 12346

📌 作用:

将 10 进制的线程号(比如 12346)转为 16 进制,用于在 jstack 中匹配线程。

🖥️ 示例输出:

303a

👉 这个 303a 就是线程 ID(nid)的 16 进制表示,你一会要在 jstack 里找这个值。

小技巧:如果线程号是 12345,那 16 进制可能是 3039;如果是 12346,就是 303a。


六、步骤 5:jstack <pid> | grep -A 20 <nid> → 导出线程栈,定位问题代码

现在,你已经知道:

  • Java 进程 PID = 12345
  • 高 CPU 线程 SPID(TID)= 12346
  • 对应的 16 进制线程号 nid = 303a

接下来,我们用 jstack 导出该 Java 进程的所有线程栈,然后找出这个线程正在执行什么代码。

🔧 命令:

jstack 12345 | grep -A 20 303a

12345 换成你的 Java 进程 PID,303a 换成你的线程 nid(16 进制线程号)

📌 作用:

  • jstack <pid>:导出该 Java 进程所有线程的调用栈(stack trace)
  • grep -A 20 303a:找出包含该线程 ID 的那一行,并显示之后的 20 行(一般足够看到线程正在执行的代码)

🧠 你会看到类似这样的输出:

"pool-1-thread-1" #12 prio=5 os_prio=0 tid=0x00007f8c1a1b8000 nid=0x303a runnable [0x00007f8c0bfff000]
   java.lang.Thread.State: RUNNABLE
        at com.fasterxml.jackson.core.JsonGenerator.writeString(JsonGenerator.java:...)
        at com.fasterxml.jackson.databind.ser.std.StringSerializer.serialize(...)
        at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(...)
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(...)
        at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(...)
        at com.fasterxml.jackson.databind.ObjectMapper._writeValue(...)
        at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(...)
        at com.your.package.YourClass.serializeObject(YourClass.java:42)
        at com.your.package.YourTask.run(YourTask.java:18)
        ...

👉 从这里你就看到了:

  • 线程名可能是 "pool-1-thread-1"(线程池里的某个线程)
  • 线程状态是 RUNNABLE,说明它正在执行
  • 最重要的是,调用栈显示它正在执行某个 对象序列化方法(比如 Jackson 的 writeValueAsString),并且定位到了你自己的代码,比如 YourClass.java:42

🎯 恭喜,你成功定位到了是哪段代码在疯狂消耗 CPU!


✅ 总结:完整排查链条(面试回答可用)

  1. 首先通过 top 命令发现某个 Java 进程的 CPU 使用率接近 100%;
  2. ps -ef | grep javajps -l 找到该 Java 进程的 PID;
  3. top -Hp <pid> 查看该进程中各个线程的 CPU 使用情况,找到占用高的线程(比如 SPID=12346);
  4. printf "%x\n" 12346 将线程号转为 16 进制(比如 nid=303a),因为 jstack 中线程是用 16 进制表示的;
  5. 最后用 jstack <pid> | grep -A 20 303a 导出该线程的调用栈,定位到是哪段代码(比如频繁序列化)导致的高 CPU。

🧠 面试加分提示

  • 你可以补充说:“为了更高效排查,我后来还学会了使用 Arthasthread -n 3 直接列出最忙的几个线程,或者用 thread <nid> 直接查看某个线程的栈,比 jstack + grep 更方便。”
  • 如果你用过 async-profiler、JProfiler、VisualVM 等工具,也可以提一下,它们可以做更深入的性能分析,包括热点方法、CPU 采样等。

如你感兴趣,我还可以给你演示如何用 Arthas 快速定位高 CPU 线程,或者如何对这类问题做 监控与预防,欢迎继续提问! 😊

posted on 2025-09-17 13:41  ~码铃薯~  阅读(45)  评论(0)    收藏  举报

导航