使用场景
- 针对活着的进程做本地的或远程的线程dump。
- 针对core文件做线程dump。
jstack用于生成java虚拟机当前时刻的线程快照。
线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。
另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
1. CPU飙高,load高,响应慢
- 一个请求过程中多次dump;
- 对比多次dump文件的runnable线程。如果在一直在执行同一个方法,说明有问题
2. 查找CPU占用最多的线程
- 使用命令
top -H -p pid
查看占用CUP最高的线程,找到对应的线程的ID,对应thread dump信息中的线程nid,(一个是十进制,一个是十六进制)。- 在thread dump中,根据top命令查找到的did,查找对应的线程的堆栈信息。
3. CPU使用率不高,但是响应很慢
- 进行dump,查看是否有很多thread卡在了i/o、数据库等地方,定位瓶颈。
4. 请求无法响应
- 多次dump,对比所有的runnable线程是否存在一直在执行相同的方法。
线程状态
根据enumThread.State
, java的Thread主要有以下几种状态
- NEW - 线程初始化状态,在调用start()之前的状态。 不会出现在Dump[1]中。
- RUNNABLE - 运行状态。在运行中状态,可能看到locked,表明它获得了某把锁。可分成下列两种状态
- 就绪态 - 线程已经获得执行所需的资源,等待CPU分配执行权。所有就绪态的线程存放在
就绪队列
中。 - 运行态 - 线程已经获得了CPU执行权,正在执行。(每个CPU在每个时刻只有一条运行态的线程)
- 就绪态 - 线程已经获得执行所需的资源,等待CPU分配执行权。所有就绪态的线程存放在
- BLOCKED - 受阻塞的状态,等待某个监视器锁。在
阻塞队列
中保存。处于阻塞队列中的线程会不断请求资源,一旦请求成功就进入就绪队列
,等待执行。 - WAITING - 线程调用了
Object.wait()
,Thread.join()
,LockSupport.park()
函数且无timeout参数时,线程进入等待状态。线程保存在等待队列
中。等待线程需要其他线程的指示。进入等待状态的线程会释放CPU的执行权和资源(包括锁)。 - TIMED_WAITING - 线程调用了
Thread.sleep()
,Object.wait(long)
,Thread.join(long)
,LockSupport.parkNanos()
,LockSupport.parkUntil()
函数时,线程进入超时等待状态。与WAITING的区别是,在等待一定时间后,会自动进入BLOCKED状态。 - TERMINATED - 线程执行结束(包括正常结束和异常结束)后的状态。
Thread.State源码
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called Object.wait()
* on an object is waiting for another thread to call
* Object.notify() or Object.notifyAll() on
* that object. A thread that has called Thread.join()
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
Monitor
Java主要使用Monitor实现线程之间的互斥与协作。每个Java对象有且仅有一个monitor。下图描述了线程和Monitor之间的关系,以及线程的状态转换。
[图片上传失败...(image-5032-1601690492151)]
- Entry Set - 当线程请求进入临界区时,进入Entry Set队列,处于该队列的线程在Dump中显示
waiting for monitor entry
状态。请求后两种可能。- 该monitor不被其他线程拥有,Entry Set里也没有其他等待线程。该线程成为Owner,执行临界区代码。线程进入
Runnable
状态。 - 该monitor被其他线程拥有,该线程在Entry Set 中继续等待。Dump会显示该线程处于
waiting for monitor entry
状态。
- 该monitor不被其他线程拥有,Entry Set里也没有其他等待线程。该线程成为Owner,执行临界区代码。线程进入
- The Owner - 当前线程拥有锁。
- Wait Set - 线程通过对象的
wait()
方法,释放了对象的锁,并在等待区等待被唤醒。在Dump中显示in Object.wait()
。
jstack 命令
基本命令
- jps -ml (获取PID)
- jstack [option]
jstack的基本参数:
- -F 当’jstack [-l] pid’没有相应的时候强制打印栈信息,如果直接jstack无响应时,用于强制jstack),一般情况不需要使用。
- -l 长列表。打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表,会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间)。-l 建议不要用。一般情况不需要使用。
- -m 打印java和native c/c++框架的所有栈信息。可以打印JVM的堆栈,显示上Native的栈帧,一般应用排查不需要使用。
- -h | -help 打印帮助信息。
示例:
XIAOYUC-M-C2V9:test xiaoyuc$ jps -ml
43153 MultiThreadProgramming.Chapter3.p_c_allWait.Run
43154 org.jetbrains.jps.cmdline.Launcher /Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/platform-api.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/log4j.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/slf4j-api-1.7.25.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/maven-resolver-spi-1.3.3.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/oro-2.0.8.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/asm-all-7.0.1.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/jps-model.jar:/Users/xiaoyuc/Libr
26028 org.jetbrains.jps.cmdline.Launcher /Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/platform-api.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/log4j.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/slf4j-api-1.7.25.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/maven-resolver-spi-1.3.3.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/oro-2.0.8.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/asm-all-7.0.1.jar:/Users/xiaoyuc/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/201.7846.76/IntelliJ IDEA.app/Contents/lib/jps-model.jar:/Users/xiaoyuc/Libr
43229
43261 sun.tools.jps.Jps -ml
623
XIAOYUC-M-C2V9:test xiaoyuc$ jstack 43153
2020-09-18 11:53:28
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.241-b07 mixed mode):
"DestroyJavaVM" #17 prio=5 os_prio=31 tid=0x00007f89ea02c000 nid=0x1903 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #16 daemon prio=9 os_prio=31 tid=0x00007f89ea812000 nid=0x5c03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"produce 2" #12 prio=5 os_prio=31 tid=0x00007f89ed000000 nid=0xa403 in Object.wait() [0x000070000f12c000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ac2bc90> (a java.lang.String)
at java.lang.Object.wait(Object.java:502)
at MultiThreadProgramming.Chapter3.p_c_allWait.Product.setValue(Product.java:17)
- locked <0x000000076ac2bc90> (a java.lang.String)
at MultiThreadProgramming.Chapter3.p_c_allWait.ThreadP.run(ThreadP.java:13)
"produce 1" #11 prio=5 os_prio=31 tid=0x00007f89ec818000 nid=0x5a03 in Object.wait() [0x000070000f029000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ac2bc90> (a java.lang.String)
at java.lang.Object.wait(Object.java:502)
at MultiThreadProgramming.Chapter3.p_c_allWait.Product.setValue(Product.java:17)
- locked <0x000000076ac2bc90> (a java.lang.String)
at MultiThreadProgramming.Chapter3.p_c_allWait.ThreadP.run(ThreadP.java:13)
"customer 2" #14 prio=5 os_prio=31 tid=0x00007f89e8009800 nid=0x5803 in Object.wait() [0x000070000ef26000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ac2bc90> (a java.lang.String)
at java.lang.Object.wait(Object.java:502)
at MultiThreadProgramming.Chapter3.p_c_allWait.Customer.getValue(Customer.java:15)
- locked <0x000000076ac2bc90> (a java.lang.String)
at MultiThreadProgramming.Chapter3.p_c_allWait.ThreadC.run(ThreadC.java:13)
"customer 1" #13 prio=5 os_prio=31 tid=0x00007f89e98df000 nid=0xa603 in Object.wait() [0x000070000ee23000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ac2bc90> (a java.lang.String)
at java.lang.Object.wait(Object.java:502)
at MultiThreadProgramming.Chapter3.p_c_allWait.Customer.getValue(Customer.java:15)
- locked <0x000000076ac2bc90> (a java.lang.String)
at MultiThreadProgramming.Chapter3.p_c_allWait.ThreadC.run(ThreadC.java:13)
"Service Thread" #10 daemon prio=9 os_prio=31 tid=0x00007f89e98de000 nid=0xa903 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #9 daemon prio=9 os_prio=31 tid=0x00007f89ea801800 nid=0x5503 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #8 daemon prio=9 os_prio=31 tid=0x00007f89e7001000 nid=0x3b03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=31 tid=0x00007f89eb800000 nid=0x3c03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=31 tid=0x00007f89e98ab000 nid=0x3e03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #5 daemon prio=5 os_prio=31 tid=0x00007f89e903f800 nid=0x3f03 runnable [0x000070000e70e000]
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)
- locked <0x000000076ac83750> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076ac83750> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)
"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007f89e8021800 nid=0x3603 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007f89ea003000 nid=0x4703 in Object.wait() [0x000070000e508000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ab08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076ab08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007f89e801a800 nid=0x3103 in Object.wait() [0x000070000e405000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=31 tid=0x00007f89eb004000 nid=0x3003 runnable
"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007f89e980e800 nid=0x1f07 runnable
"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007f89e7800800 nid=0x2003 runnable
"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007f89e7000000 nid=0x5403 runnable
"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007f89e9001800 nid=0x2b03 runnable
"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007f89e7002800 nid=0x5103 runnable
"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007f89ea001000 nid=0x4f03 runnable
"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007f89ea800000 nid=0x4d03 runnable
"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007f89ea801000 nid=0x4b03 runnable
"GC task thread#8 (ParallelGC)" os_prio=31 tid=0x00007f89e7003000 nid=0x2d03 runnable
"GC task thread#9 (ParallelGC)" os_prio=31 tid=0x00007f89e9002000 nid=0x2f03 runnable
"VM Periodic Task Thread" os_prio=31 tid=0x00007f89eb801000 nid=0xa703 waiting on condition
JNI global references: 317
Dump分析
Dump中的关键状态
1. Waiting on condition
等待资源,或等待某个条件的发生。可能原因如下:
- 如果堆栈信息显示是应用代码,证明该线程在等待资源。一般是大量读取某资源,且该资源采用了资源锁的情况下,线程进入等待状态。
- 正在等待其他线程的执行。
- 如果发现大量线程在Waiting on condition状态,从线程stack看,正等待网络读写,这可能是一个网络瓶颈。
- 一种情况是网络非常忙,网络带宽几乎消耗完了,仍有大量数据等待网络读写。
- 另一种是网络空闲,但是由于路由等问题,导致包无法正常到达。
- 线程正在sleep。
2. Waiting for Monitor Entry 和 in Object.wait()
详见Monitor章节
3. Deadlock
线程死锁,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。
4. Blocked
线程长时间未获得资源,被容器的线程管理器标识为阻塞状态。
参考资料
- Java运行状态分析1: 线程及线程状态
- Java运行状态分析2: 获取线程状态及堆栈信息
- Java命令——jstack工具
- jstack查看jvm线程状态
- Java常见命令及Java Dump介绍
- 啃碎并发(四):Java线程Dump分析
- Java线程Dump的分析——jstack pid
- Java多线程(二)——Monitor
-
Java虚拟机的运行时快照。将Java虚拟机运行时的状态和信息保存到文件。堆Dump,包含线程Dump,幵包含所有堆对象的状态。二进制格式。线程Dump,包含所有线程的运行状态。纯文本格式。线程栈是瞬时记录,一般都需要结合程序的日志进行跟踪问题。 ↩