对于运行在 VPS 或公有云虚拟机上的 Java 站点而言,性能问题尤其是高 CPU 占用是站长经常遇到的挑战。当应用卡死或响应缓慢时,我们不能贸然重启,而是需要快速定位根源。jps 和 jstack 是 JDK 自带的两个强大工具,它们能帮助我们无侵入地诊断 JVM 内部的线程状态。
本文将以一个线上环境 CPU 满载的场景为例,演示如何结合 Linux 命令和 JVM 工具找到导致高 CPU 占用的具体代码行。
步骤一:使用 jps 和 top 确定问题进程和线程
首先,我们使用 jps 找到正在运行的 Java 进程 ID (PID)。
jps -l
# 假设输出如下,我们确定应用的主进程 PID 是 12345
12345 com.example.MyWebAppRunner
34567 sun.tools.jps.Jps
接下来,我们使用 top 命令并结合 -H 参数,查看 PID 12345 下具体是哪个线程(Lightweight Process, LWP)占用了最高的 CPU 资源。-H 参数会将 top 切换到线程视图。
top -H -p 12345
在 top 的输出中,我们重点关注 TID 列(即线程ID,或 LWP)和 %CPU 列。假设我们观察到线程 ID 为 9876 的线程,其 CPU 占用率接近 100%。
步骤二:转换线程 ID 为十六进制
jstack 在其输出中识别线程使用的是 Native ID (NID) 的十六进制格式。因此,我们需要将上一步中找到的十进制 LWP 9876 转换为十六进制。
可以使用 printf 命令快速完成转换:
# 将十进制 9876 转换为十六进制
printf "0x%x\n" 9876
# 输出: 0x2694
现在,我们得到了目标线程的十六进制 NID:0x2694。
步骤三:使用 jstack 生成线程快照并分析
利用 jstack 命令,我们对 PID 12345 生成线程堆栈快照,并将结果保存到文件中。
jstack 12345 > /tmp/thread_dump_12345.txt
打开 /tmp/thread_dump_12345.txt 文件,搜索我们之前转换得到的 NID 0x2694。
grep -A 20 'nid=0x2694' /tmp/thread_dump_12345.txt
你将会找到类似以下的堆栈信息片段:
"Heavy CPU Worker Thread" daemon prio=10 tid=0x00007f3d9c025000 nid=0x2694 runnable [0x00007f3d537f5000]
java.lang.Thread.State: RUNNABLE
at com.example.analysis.BadLoopUtil.infiniteCalc(BadLoopUtil.java:45)
at com.example.analysis.CPUAnalyzer.run(CPUAnalyzer.java:22)
at java.lang.Thread.run(Thread.java:748)
分析结果:
- 线程状态 (State): RUNNABLE 表示该线程正在运行,或随时可以运行,且没有被阻塞或等待锁。高 CPU 占用的线程通常处于 RUNNABLE 状态。
- 堆栈追踪 (Stack Trace): 堆栈信息清晰地指向了 com.example.analysis.BadLoopUtil.infiniteCalc(BadLoopUtil.java:45) 这一行代码。这通常意味着该方法内部存在一个计算密集型的操作,如一个没有退出条件的循环(死循环),或者低效的算法,这就是导致 CPU 占满的罪魁祸首。
总结
通过 jps 识别 Java 进程,top -H 识别高 CPU 线程,LWP 转换,再到 jstack 精确定位代码行,我们可以在不重启服务、不影响用户体验的情况下,快速锁定线上应用性能瓶颈。掌握这一套组合拳,对于任何管理 VPS 或虚拟机上 Java 应用的站长都至关重要。
汤不热吧