虚拟机性能监控与故障处理工具-JDK的命令行工具-jstack:Java堆栈跟踪工具
一、理论知识
(一)jstack是什么?
jstack(Stack Trace for Java),用于生成Java虚拟机当前时刻的线程快照(该线程快照一般也称为:threaddump或者javacore文件)。
jstack是Java虚拟机自带的一种堆栈跟踪工具,用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。
(二)jstack有什么用处?
线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待
等都是导致线程长时间停顿的常见原因。
线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
如果Java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而知道java程序是如何崩溃和在程序何处发生问题。
另外,jstack工具还可以附属到正在运行的Java程序中,看到当时运行的java程序的java stack和native stack的信息,。如果现在运行的Java程序呈现hung的状态,jstack是非常有用的。
jstack主要分为两个功能:(1)针对活着的进程做本地的或远程的线程dump;(2) 针对core文件做线程dump。
一般情况下,通过jstack输出的线程信息主要包括:jvm自身线程、用户线程等。其中jvm线程会在jvm启动时就会存在。对于用户线程则是在用户访问时才会生成。
(三)jstack怎么用?
1、jstack命令格式
jstack [ option ] pid
# 重定向将输出保存到文件。
jstack -l [Java进程PID] > jstack202005021345.txt
2、参数列表:
选项 | 作用 |
---|---|
[ option ] | 详细见下面的option选项 |
pid | 需要被打印配置信息的java进程id,可以用jps查询得到。 |
3、option选项列表:
选项 | 作用 |
---|---|
-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) | 打印帮助信息。 |
详细见下面“实践”章节和“案例实战”章节。
(四)线程dump的分析工具:
- IBM Thread and Monitor Dump Analyze for Java 一个小巧的Jar包,能方便的按状态,线程名称,线程停留的函数排序,快速浏览。
- http://spotify.github.io/threaddump-analyzer Spotify提供的Web版在线分析工具,可以将锁或条件相关联的线程聚合到一起。
二、实践
(一)死锁问题
1、问题描述:
程序响应很慢,一直卡住。很可能是程序出现了死锁。
2、原因分析:
造成死锁的原因很可能是多线程的程序代码对synchronize使用不当,同时锁住了对方需要的资源。
3、排查步骤:
(1)使用jps
查看线程ID
(2)使用jstack pid
:查看进程情况。或者输出到文件中,命令参考如下:
# 重定向将输出保存到文件。
jstack -l [Java进程PID] > jstack202005021345.txt
(3)分析线程堆栈
详见后面的第4步骤。
(二)CPU Load过高
1、问题描述:
CPU Load过高,如何排查原因?
2、原因分析:
造成CPU Load过高的原因,一般是以下几种:Full GC次数增大、代码存在Bug(例如死循环、正则的不巧当使用)等
3、排查步骤:
(1)top
查找出哪个进程消耗的cpu高。执行top命令,默认是进程视图,其中PID是进程号
如果确定是java进程,执行命令jps -v
,查看Java进程PID
(2)top -Hp [Java进程PID]
查看当前进程下最消耗CPU的线程,获得线程ID
(3)printf “%x\n” [步骤(2)中获得的线程ID]
得到线程ID的16进制表示
(4)jstack [Java进程PID] | grep -A100 [步骤(3)中获得的线程ID]
查看线程堆栈,定位代码行。
在工作中,生产环境只有运维有权限操作,所以常用的做法是让运维同事直接导入该进程的所有线程堆栈,命令为:
jstack -l [Java进程PID] > jstack202005021345.txt
(5)分析线程堆栈
详见后面的第4步骤。
4、分析线程堆栈
(1)准备知识:线程状态
想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:
线程状态 | 说明 |
---|---|
NEW | 未启动的。不会出现在Dump中。 |
RUNNABLE | 在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。 |
BLOCKED | 受阻塞并等待监视器锁。被某个锁(synchronizers)给block住了。 |
WATING | 无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。 |
TIMED_WATING | 有时限的等待另一个线程的特定操作。和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。 |
TERMINATED | 已退出的。 |
(2)准备知识:Monitor
在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下 面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图:
进入区(Entrt Set) | 表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则迚入拥有者;否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。 |
拥有者(The Owner) | 表示某一线程成功竞争到对象锁。 |
等待区(Wait Set) | 表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。 |
从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在“Wait Set”中等待的线程状态是 “in Object.wait()”。 先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。对应的 code就像:
synchronized(obj) {
.........
}
(3)开始分析:调用修饰
表示线程在方法调用时,额外的重要的操作。线程Dump分析的重要信息。修饰上方的方法调用。
locked <地址> 目标 | 使用synchronized申请对象锁成功,监视器的拥有者。 |
waiting to lock <地址> 目标 | 使用synchronized申请对象锁未成功,在迚入区等待。 |
waiting on <地址> 目标 | 使用synchronized申请对象锁成功后,释放锁幵在等待区等待。 |
parking to wait for <地址> 目标 | 需与堆栈中的”parking to wait for (atjava.util.concurrent.SynchronousQueue$TransferStack)”结合来看。first–>此线程是在等待某个条件的发生,来把自己唤醒,second–>SynchronousQueue不是一个队列,其是线程之间移交信息的机制,当我们把一个元素放入到 SynchronousQueue 中时必须有另一个线程正在等待接受移交的任务,因此这就是本线程在等待的条件。 |
locked
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement
通过synchronized关键字,成功获取到了对象的锁,成为监视器的拥有者,在临界区内操作。对象锁是可以线程重入的。
waiting to lock
at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
- waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
at com.jiuqi.dna.core.impl.ContextImpl.find
at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo
通过synchronized关键字,没有获取到了对象的锁,线程在监视器的进入区等待。在调用栈顶出现,线程状态为Blocked。
waiting on
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
- locked <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run
通过synchronized关键字,成功获取到了对象的锁后,调用了wait方法,进入对象的等待区等待。在调用栈顶出现,线程状态为WAITING或TIMED_WATING。
parking to wait for
park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包会出现的新的机制,不synchronized体系不同。
(4)开始分析:线程状态产生的原因
线程状态 | 产生的原因 |
---|---|
runnable | 状态一般为RUNNABLE。 |
in Object.wait() | 等待区等待,状态为WAITING或TIMED_WAITING。 |
waiting for monitor entry | 进入区等待,状态为BLOCKED。 |
waiting on condition | 等待区等待、被park。 |
sleeping | sleeping |
Wait on condition 该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。 最常见的情况就是线程处于sleep状态,等待被唤醒。 常见的情况还有等待网络IO:在java引入nio之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。在 NewIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 CPU时间,相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。
runnable
in Object.wait()
waiting for monitor entry
待补充。
waiting on condition
sleeping
待补充。
三、案例实战
(一)案例1:空的无限循环
1、准备:JStackDemo1.java
package jstack;
public class JStackDemo1 {
public static void main(String[] args) {
while (true) {
//Do Nothing
}
}
}
2、第一步:jps查看进程号
D:\1未备份\davecode\study\架构师领域\java\jvm>jps
13584
8980 JStackDemo1
11336 RemoteMavenServer
17880 Jps
可以看到,JStackDemo1程序的进程号为8980
3、第二步:jstack 查看堆栈信息
D:\1未备份\davecode\study\架构师领域\java\jvm>jstack 8980
2020-05-05 16:32:12
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019986800 nid=0xa48 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000000198e8000 nid=0x1d8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000198e5000 nid=0x1f44 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000000198e3800 nid=0x368c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000000198df000 nid=0x27ec waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000000198a9800 nid=0x1c40 runnable [0x000000001ae7e000]
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 <0x00000000d608d230> (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 <0x00000000d608d230> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001983e000 nid=0xbbc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001815e800 nid=0x2ed0 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000018153000 nid=0x4604 in Object.wait() [0x000000001ab7f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000d5f08ed0> (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=2 tid=0x0000000019823000 nid=0x2b08 in Object.wait() [0x000000001aa7e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"main" #1 prio=5 os_prio=0 tid=0x0000000002ed3800 nid=0x11ec runnable [0x0000000002d0f000]
java.lang.Thread.State: RUNNABLE
at deadlock.JStackDemo1.main(JStackDemo1.java:6)
"VM Thread" os_prio=2 tid=0x0000000018146800 nid=0x38e8 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002ee9000 nid=0x2378 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002eea800 nid=0x1b00 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002eec000 nid=0x2bd0 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002eee800 nid=0x3570 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002ef1000 nid=0x1100 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002ef2000 nid=0x17dc runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002ef5000 nid=0x4ac runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002ef6800 nid=0x3f8 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002ef7800 nid=0x3988 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002ef8800 nid=0x120 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x00000000199d1800 nid=0x3a94 waiting on condition
JNI global references: 12
从这段堆栈信息中可以看到,当前一共有一条用户级别线程,线程处于runnable状态,执行到JStackDemo1.java的第6行。
"main" #1 prio=5 os_prio=0 tid=0x0000000002ed3800 nid=0x11ec runnable [0x0000000002d0f000]
java.lang.Thread.State: RUNNABLE
at deadlock.JStackDemo1.main(JStackDemo1.java:6)
(二)案例2:线程无限循环打印
1、准备:Thread1.java和JStackDemo2.java
Thread1.java
package jstack;
public class Thread1 implements Runnable{
public void run() {
while(true){
System.out.println(1);
}
}
}
JStackDemo2.java
package jstack;
public class JStackDemo2 {
public static void main(String[] args) {
Thread thread = new Thread(new Thread1());
thread.start();
}
}
2、第一步:jps查看进程号
D:\1未备份\davecode\study\架构师领域\java\jvm>jps
12544 JStackDemo1
13584
11336 RemoteMavenServer
14760 Launcher
14012 Jps
9084 JStackDemo2
可以看到,JStackDemo2程序的进程号为9084。
3、第二步:jstack 查看堆栈信息
D:\1未备份\davecode\study\架构师领域\java\jvm>jstack 9084
2020-05-05 16:50:16
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x000000001985d800 nid=0x3acc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-0" #12 prio=5 os_prio=0 tid=0x000000001985c800 nid=0x32b4 runnable [0x000000001b41f000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
- locked <0x0000000081c4bbc0> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:482)
- locked <0x0000000081c066a8> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x0000000081c06668> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
- eliminated <0x0000000081c066a8> (a java.io.PrintStream)
at java.io.PrintStream.print(PrintStream.java:597)
at java.io.PrintStream.println(PrintStream.java:736)
- locked <0x0000000081c066a8> (a java.io.PrintStream)
at deadlock.Thread1.run(Thread1.java:6)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019807800 nid=0x4240 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x0000000019767800 nid=0x2428 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000000019764800 nid=0x4530 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000019762800 nid=0x3214 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000019729000 nid=0x3954 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000019761000 nid=0x29d8 runnable [0x000000001ad1e000]
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 <0x0000000081c04c48> (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 <0x0000000081c04c48> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x00000000196bf000 nid=0x34a8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019712000 nid=0x2b8c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000002d4c800 nid=0x43b0 in Object.wait() [0x000000001aa1f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000081c08760> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x0000000081c08760> (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=2 tid=0x00000000196a3000 nid=0x864 in Object.wait() [0x000000001a91e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x0000000019682800 nid=0x3894 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002c69000 nid=0x23d8 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002c6a800 nid=0x3370 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002c6c000 nid=0x3be4 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002c6e800 nid=0x272c runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002c71000 nid=0x47a0 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002c72000 nid=0x3548 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002c75000 nid=0x21b4 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002c76800 nid=0x85c runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002c77800 nid=0x2e38 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002c78800 nid=0x43d0 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001978b000 nid=0x3804 waiting on condition
JNI global references: 12
从这段堆栈信息中可以看到,
线程的状态: WAITING 线程的调用栈
线程的当前锁住的资源: <0x0000000081c0e300>
线程当前等待的资源:<0x0000000081c0e300>
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000196a3000 nid=0x864 in Object.wait() [0x000000001a91e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
为什么同时锁住和等待同一个资源:
因为线程的执行中,先获得了这个对象的 Monitor(对应于 locked <0x0000000081c0e300>)。
当执行到 obj.wait(),线程即放弃了 Monitor的所有权,进入 “wait set”队列(对应于 waiting on <0x0000000081c0e300> )。
(三)案例3:线程Dump的分析
1、进入区等待
"d&a-3588" daemon waiting for monitor entry [0x000000006e5d5000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
- waiting to lock <0x0000000602f38e90> (a java.lang.Object)
at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
关键字:waiting for monitor entry
、waiting to lock
线程状态BLOCKED,线程动作wait on monitor entry,调用修饰waiting to lock总是一起出现。表示在代码级别已经存在冲突的调用。必然有问题的代码,需要尽可能减少其发生。
2、同步块阻塞
"blocker" runnable
java.lang.Thread.State: RUNNABLE
at com.jiuqi.hcl.javadump.Blocker$1.run(Blocker.java:23)
- locked <0x00000000eb8eff68> (a java.lang.Object)
"blockee-11" waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
- waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
"blockee-86" waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
- waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
关键字:waiting for monitor entry
、waiting to lock
、BLOCKED
、waiting to lock
一个线程锁住某对象,大量其他线程在该对象上等待。
3、持续运行的IO
"d&a-614" daemon prio=6 tid=0x0000000022f1f000 nid=0x37c8 runnable
[0x0000000027cbd000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(Unknown Source)
at oracle.net.ns.Packet.receive(Packet.java:240)
at oracle.net.ns.DataPacket.receive(DataPacket.java:92)
at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:172)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:117)
at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1034)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:588)
关键字:RUNNABLE
、SocketInputStream.socketRead0
、jdbc
IO操作是可以以RUNNABLE状态达成阻塞。例如:数据库死锁、网络读写。 格外注意对IO线程的真实状态的分析。 一般来说,被捕捉到RUNNABLE的IO调用,都是有问题的。
以上堆栈显示: 线程状态为RUNNABLE。 调用栈在SocketInputStream或SocketImpl上,socketRead0等方法。 调用栈包含了jdbc相关的包。很可能发生了数据库死锁。
4、分线程调度的休眠
正常的线程池等待:关键字:in Object.wait()
、TIMED_WAITING
"d&a-131" in Object.wait()
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo(WorkingManager.java:322)
- locked <0x0000000313f656f8> (a com.jiuqi.dna.core.impl.WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run(WorkingThread.java:40)
可疑的线程等待,关键字:in Object.wait()
、WAITING
、at com.jiuqi.dna.core.impl.Transaction.lock()
"d&a-121" in Object.wait()
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at com.jiuqi.dna.core.impl.AcquirableAccessor.exclusive()
- locked <0x00000003011678d8> (a com.jiuqi.dna.core.impl.CacheGroup)
at com.jiuqi.dna.core.impl.Transaction.lock()
5、小结
wait on monitor entry: 被阻塞的,肯定有问题
runnable : 注意IO线程
in Object.wait(): 注意非线程池等待
(四)案例4:死锁分析
所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
1、准备
(1)JStackDemo3.java
package jstack;
public class JStackDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLockclass(true));//建立一个线程
Thread t2 = new Thread(new DeadLockclass(false));//建立另一个线程
t1.start();//启动一个线程
t2.start();//启动另一个线程
}
}
(2)DeadLockclass.java
package jstack;
class DeadLockclass implements Runnable {
public boolean falg;// 控制线程
DeadLockclass(boolean falg) {
this.falg = falg;
}
public void run() {
/**
* 如果falg的值为true则调用t1线程
*/
if (falg) {
while (true) {
synchronized (Suo.o1) {
System.out.println("o1 " + Thread.currentThread().getName());
synchronized (Suo.o2) {
System.out.println("o2 " + Thread.currentThread().getName());
}
}
}
}
/**
* 如果falg的值为false则调用t2线程
*/
else {
while (true) {
synchronized (Suo.o2) {
System.out.println("o2 " + Thread.currentThread().getName());
synchronized (Suo.o1) {
System.out.println("o1 " + Thread.currentThread().getName());
}
}
}
}
}
}
(2)Suo.java
package jstack;
class Suo {
static Object o1 = new Object();
static Object o2 = new Object();
}
2、启动程序
当启动该程序时,看一下控制台:
"C:\Program Files\java\jdk1.8.0_191\bin\java.exe" -javaagent:D:\davesoftware\ideaIU-2018.3.win\lib\idea_rt.jar=62726:D:\davesoftware\ideaIU-2018.3.win\bin -Dfile.encoding=UTF-8 -classpath "C:\Program Files\java\jdk1.8.0_191\jre\lib\charsets.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\deploy.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\localedata.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunec.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\javaws.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jce.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jfr.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jfxswt.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jsse.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\management-agent.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\plugin.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\resources.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\rt.jar;D:\1未备份\davecode\study\架构师领域\java\jvm\target\classes" jstack.JStackDemo3
o1 Thread-0
o2 Thread-1
可以发现,程序只输出了两行内容,然后程序就不再打印其它的东西了,但是程序并没有停止。这样就产生了死锁。 当线程1使用synchronized
锁住了o1的同时,线程2也是用synchronized
锁住了o2。当两个线程都执行完第一个打印任务的时候,线程1想锁住o2,线程2想锁住o1。但是,线程1当前锁着o1,线程2锁着o2。所以两个想成都无法继续执行下去,就造成了死锁。
3、第一步:jps查看进程号
D:\1未备份\davecode\study\架构师领域\java\jvm>jps
13216 KotlinCompileDaemon
13584
15520 JStackDemo3
5456 Launcher
16116 Jps
11336 RemoteMavenServer
可以看到,JStackDemo3程序的进程号为15520。
4、第三步:jstack 查看堆栈信息
D:\1未备份\davecode\study\架构师领域\java\jvm>jstack 15520
2020-05-05 17:27:14
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000002ac3800 nid=0x718 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #13 prio=5 os_prio=0 tid=0x00000000195b4800 nid=0x2624 waiting for monitor entry [0x000000001b27f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at jstack.DeadLockclass.run(DeadLockclass.java:30)
- waiting to lock <0x00000000d62e8978> (a java.lang.Object)
- locked <0x00000000d62e8988> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #12 prio=5 os_prio=0 tid=0x00000000195b3800 nid=0x578 waiting for monitor entry [0x000000001b17f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at jstack.DeadLockclass.run(DeadLockclass.java:17)
- waiting to lock <0x00000000d62e8988> (a java.lang.Object)
- locked <0x00000000d62e8978> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019500800 nid=0x2134 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000000194d5000 nid=0x402c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000194d2000 nid=0x47c4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000000194d0800 nid=0x3d50 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000000194c6000 nid=0x1970 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000000194b5800 nid=0x3978 runnable [0x000000001aa7e000]
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 <0x00000000d608d230> (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 <0x00000000d608d230> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001942e000 nid=0x8d0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000017d4e800 nid=0x43f0 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000017d43000 nid=0x214c in Object.wait() [0x000000001a77f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000d5f08ed0> (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=2 tid=0x0000000019413000 nid=0x37f8 in Object.wait() [0x000000001a67f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x0000000017d36800 nid=0x2d18 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002ad9000 nid=0xb94 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002ada800 nid=0x26ec runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002adc000 nid=0x3ca8 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002ade800 nid=0x3ab4 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002ae1000 nid=0x22bc runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002ae2000 nid=0x2844 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002ae5000 nid=0x2450 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002ae6800 nid=0x3d84 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002ae7800 nid=0x3e60 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002ae8800 nid=0x19ac runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000019501000 nid=0x2be8 waiting on condition
JNI global references: 12
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0000000017d3fe28 (object 0x00000000d62e8978, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000017d42608 (object 0x00000000d62e8988, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at jstack.DeadLockclass.run(DeadLockclass.java:30)
- waiting to lock <0x00000000d62e8978> (a java.lang.Object)
- locked <0x00000000d62e8988> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at jstack.DeadLockclass.run(DeadLockclass.java:17)
- waiting to lock <0x00000000d62e8988> (a java.lang.Object)
- locked <0x00000000d62e8978> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
堆栈写的很明显,它告诉我们 Found one Java-level deadlock
,然后指出造成死锁的两个线程的内容。然后,又通过 Java stack information for the threads listed above
来显示更详细的死锁的信息。 他说
Thread-1在想要执行第40行的时候,当前锁住了资源<0x00000007d6aa2ca8>
,但是他在等待资源<0x00000007d6aa2c98>
Thread-0在想要执行第27行的时候,当前锁住了资源<0x00000007d6aa2c98>
,但是他在等待资源<0x00000007d6aa2ca8>
由于这两个线程都持有资源,并且都需要对方的资源,所以造成了死锁。 原因我们找到了,就可以具体问题具体分析,解决这个死锁了。
(五)小结
虚拟机执行Full GC时,会阻塞所有的用户线程。因此,即时获取到同步锁的线程也有可能被阻塞。 在查看线程Dump时,首先查看内存使用情况。
对于jstack做的ThreadDump的栈,可以反映如下信息(一个load飙高的过程分析,非常有价值)
1. 如果某个相同的call stack经常出现, 我们有80%的以上的理由确定这个代码存在性能问题(读网络的部分除外);
2. 如果相同的call stack出现在同一个线程上(tid)上, 我们很很大理由相信, 这段代码可能存在较多的循环或者死循环;
3. 如果某call stack经常出现, 并且里面带有lock,请检查一下这个lock的产生的原因, 可能是全局lock造成了性能问题;
4. 在一个不大压力的群集里(w<2), 我们是很少拿到带有业务代码的stack的, 并且一般在一个完整stack中, 最多只有1-2业务代码的stack,
5. 如果经常出现, 一定要检查代码, 是否出现性能问题。
6. 如果你怀疑有dead lock问题, 那么请把所有的lock id找出来,看看是不是出现重复的lock id。
jstack -m 会打印出JVM堆栈信息,涉及C、C++部分代码,可能需要配合gdb命令来分析。