虚拟机性能监控与故障处理工具-JDK的命令行工具-jstack:Java堆栈跟踪工具

虚拟机性能监控与故障处理工具-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的分析工具:

二、实践

(一)死锁问题

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之间关系,以及线程的状态转换图:

img

进入区(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 entrywaiting 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 entrywaiting to lockBLOCKEDwaiting 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)

关键字:RUNNABLESocketInputStream.socketRead0jdbc

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()WAITINGat 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命令来分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章