杰瑞科技汇

Linux下Java线程查看方法有哪些?

核心概念:Java 线程 vs. 操作系统线程

首先要理解,一个 Java 线程通常最终会映射到一个操作系统(Linux)的轻量级进程(LWP - Lightweight Process)上,Java 线程的状态(RUNNABLE, BLOCKED, WAITING 等)与操作系统线程的运行状态不完全对应,这是我们分析时需要注意的。

Linux下Java线程查看方法有哪些?-图1
(图片来源网络,侵删)

jstack - 最常用、最直接

jstack (Java Stack Trace) 是 JDK 自带的工具,专门用于生成 Java 虚拟机中各个线程的堆栈跟踪信息,这是排查线程问题(如死锁、长时间运行、CPU 100%)的首选方法。

找到 Java 进程 ID (PID)

你需要查看正在运行的 Java 进程。

# 使用 ps 命令
ps -ef | grep java
# 或者使用 pgrep 命令(更简洁)
pgrep -f java

输出示例:

# ps -ef | grep java
myuser   12345  1234  99 10:30 pts/0  00:15:30 java -jar my-application.jar
myuser   12350  1231  0 10:31 pts/1  00:00:00 grep --color=auto java

这里的 12345 就是我们要找的 Java 进程的 PID。

Linux下Java线程查看方法有哪些?-图2
(图片来源网络,侵删)

使用 jstack 生成线程快照

# 基本用法
jstack <PID> > thread_dump.txt
# 示例
jstack 12345 > thread_dump_20251027.txt

分析 jstack 输出

打开生成的 thread_dump.txt 文件,你会看到类似下面的内容:

"main" #1 prio=5 os_prio=0 tid=0x00007f8c1c000000 nid=0x1234 runnable [0x00007f8c2a2d0000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        at java.io.InputStreamReader.read(InputStreamReader.java:167)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.example.MyApplication.main(MyApplication.java:25)
"pool-1-thread-1" #10 prio=5 os_prio=0 tid=0x00007f8c1c00a000 nid=0x1235 waiting on condition [0x00007f8c292c9000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000d1bf2c50> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.Condition.await(Condition.java:203)
        at java.util.concurrent.ArrayBlockingQueue.take(ArrayBlockingQueue.java:403)
        at com.example.TaskWorker.run(TaskWorker.java:30)
        at java.util.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

如何解读:

  • "main": 线程的名称。
  • #1: 线程 ID (JVM 内部 ID)。
  • nid=0x1234: 线程 ID (操作系统 Native ID),nid 是 Native ID 的缩写,这个 ID 在 toppidstat 中可以看到。
  • java.lang.Thread.State: RUNNABLE: 这是最重要的信息,它告诉你线程当前在 JVM 中的状态。
    • RUNNABLE: 线程正在执行或准备执行。
    • WAITING (parking): 线程在等待某个条件,Object.wait()LockSupport.park()
    • TIMED_WAITING: 线程在等待某个条件,但设置了超时,Thread.sleep()Lock.tryLock(long, TimeUnit)
    • BLOCKED (on object monitor): 线程在等待获取一个锁,被阻塞了。
    • TERMINATED: 线程已执行完毕。
  • 后续的堆栈跟踪:显示了线程在执行到当前状态时,所有的方法调用路径,这对于定位问题代码至关重要。

jconsole / VisualVM - 图形化界面分析

如果你更喜欢图形化界面,可以使用 JDK 自带的 jconsole 或更强大的 VisualVM

启动工具

# jconsole
jconsole
# VisualVM (推荐,功能更全)
jvisualvm

启动后,在左侧的进程列表中选择你要监控的 Java 应用。

Linux下Java线程查看方法有哪些?-图3
(图片来源网络,侵删)

在 VisualVM 中查看线程

  1. 连接到进程:双击进程。
  2. 切换到 "Threads" 标签页
    • 线程概览:你可以看到所有线程的列表,包括它们的名称、状态(RUNNABLE, WAITING 等)和 CPU 时间。
    • 线程 Dump:点击 "Thread Dump" 按钮,可以生成当前的线程快照,内容与 jstack 类似,但格式化得更好。
    • 死锁检测:如果发生死锁,VisualVM 会自动检测并高亮显示死锁的线程和它们等待的锁。
    • CPU 分析:可以查看哪个线程占用了最多的 CPU 时间,这对于定位 CPU 高耗用问题非常有用。

top / htop - 结合 OS 线程 ID

这种方法可以让你从操作系统层面快速定位到哪个 Java 线程消耗了最多的 CPU。

使用 top 查看高 CPU 占用进程

top -p <PID>

top -p 12345,你会看到类似这样的输出:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM    TIME+  COMMAND
12345 myuser    20   0 1.5g  800m  100m R  99.9   5.0  15:30.23 java

%CPU 列显示了进程的 CPU 使用率,如果很高,说明 Java 进程内部有线程在疯狂计算。

转换为 OS 线程 ID 并排序

要看到是哪个线程在占用 CPU,需要使用 -H 参数(显示线程)和 -p 参数(指定进程)。

top -H -p <PID>

输出示例:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM    TIME+  COMMAND
12356 myuser    20   0 1.5g  800m  100m R  99.9   5.0  15:30.23 java  <-- 这是我们要找的线程
12345 myuser    20   0 1.5g  800m  100m S   0.1   5.0  15:30.23 java

这里的 12356 就是操作系统层面的线程 ID (LWP ID)。

关联 jstack 输出

你有了 OS 线程 ID 12356,回到 jstack 的输出文件中,查找 nid=0x12356 (注意,top 的 PID 是十进制,jstacknid 是十六进制)。

# 使用 grep 搜索
grep "nid=0x12356" thread_dump.txt

或者直接搜索线程名(如果线程名有特征),这样你就能精确定位到是哪段 Java 代码导致了高 CPU。


/proc 文件系统 - Linux 原生方法

Linux 的 /proc 文件系统提供了一个虚拟的文件系统来访问内核数据,你可以直接读取它来获取线程信息。

查看进程下的所有线程

一个进程的所有线程都列在 /proc/<PID>/task/ 目录下。

# 列出 PID 12345 的所有线程 ID
ls /proc/12345/task/

这会返回所有线程的 OS ID 列表。

查看单个线程的栈

对于 /proc/<PID>/task/<TID>/ 目录,你可以查看其 stack 文件,这通常和 jstack 的输出类似。

# 查看 TID 12356 的栈
cat /proc/12345/task/12356/stack

这个输出通常比 jstack 简洁,直接显示了内核态的调用栈,对于分析某些特定问题(如系统调用阻塞)很有帮助。


总结与最佳实践

方法 优点 缺点 适用场景
jstack 最常用,信息全面,输出可保存,易于脚本化 需要手动操作,是静态快照 日常线程问题排查,如死锁、长时间等待、CPU 高占用后的代码定位
jconsole/VisualVM 图形化界面,直观,可实时监控,内置死锁检测 需要图形界面,可能对性能有轻微影响 实时监控快速定位死锁初步分析 CPU/内存
top/htop 快速找到高 CPU 占用的 OS 线程,无需安装额外工具 信息有限,需要手动关联到 Java 代码 快速响应 CPU 100% 的紧急情况,先找到“罪魁祸首”线程
/proc Linux 原生,无需任何工具,信息来自内核 输出格式不易读,需要了解 Linux 内核知识 深入系统层面分析,或在无法安装 JDK 工具的受限环境中使用

推荐的工作流程:

  1. 发现问题:通过 tophtop 发现 Java 进程 CPU 占用异常高。
  2. 定位线程:使用 top -H -p <PID> 找到具体是哪个 OS 线程(PID)在消耗 CPU。
  3. 生成快照:使用 jstack <PID> 生成一份完整的线程快照。
  4. 分析关联:将 top 中找到的 OS 线程 ID(转换为十六进制)在 jstack 输出中搜索,定位到具体的 Java 代码和方法。
  5. 持续监控:如果问题复杂,使用 VisualVM 进行持续监控和更深入的分析。
分享:
扫描分享到社交APP
上一篇
下一篇