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?
那么典型的排查路径是:
- 找到高 CPU 的 Java 进程
- 找到该进程中哪个线程占用 CPU 高
- 把线程号转为 16 进制(因为 jstack 中的线程 ID 是 16 进制的)
- 用 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=12345 的 java 进程 CPU 使用率是 99.9%,基本就是它了!
三、步骤 2:ps -ef | grep java 或 jps → 找到 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!
✅ 总结:完整排查链条(面试回答可用)
- 首先通过
top命令发现某个 Java 进程的 CPU 使用率接近 100%;- 用
ps -ef | grep java或jps -l找到该 Java 进程的 PID;- 用
top -Hp <pid>查看该进程中各个线程的 CPU 使用情况,找到占用高的线程(比如 SPID=12346);- 用
printf "%x\n" 12346将线程号转为 16 进制(比如 nid=303a),因为 jstack 中线程是用 16 进制表示的;- 最后用
jstack <pid> | grep -A 20 303a导出该线程的调用栈,定位到是哪段代码(比如频繁序列化)导致的高 CPU。
🧠 面试加分提示
- 你可以补充说:“为了更高效排查,我后来还学会了使用 Arthas 的
thread -n 3直接列出最忙的几个线程,或者用thread <nid>直接查看某个线程的栈,比 jstack + grep 更方便。” - 如果你用过 async-profiler、JProfiler、VisualVM 等工具,也可以提一下,它们可以做更深入的性能分析,包括热点方法、CPU 采样等。
如你感兴趣,我还可以给你演示如何用 Arthas 快速定位高 CPU 线程,或者如何对这类问题做 监控与预防,欢迎继续提问! 😊
浙公网安备 33010602011771号