jstack
jstack是java虚拟机自带的一种堆栈跟踪工具。
功能
jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
线程出现停顿的时候,通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。
另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
线程状态
使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:
NEW 未启动的。不会出现在Dump中。
RUNNABLE 在虚拟机内执行的。
BLOCKED 受阻塞并等待监视器锁。
WATING 无限期等待另一个线程执行特定操作。
TIMED_WATING 有时限的等待另一个线程的特定操作。
TERMINATED 已退出的。
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()”。
调用修饰
表示线程在方法调用时,额外的重要的操作。线程Dump分析的重要信息。修饰上方的方法调用。
locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者 waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在进入区等待 waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放锁幵在等待区等待 parking to wait for <地址> 目标
locked
"Thread-0" #11 prio=5 os_prio=0 cpu=0.22ms elapsed=14.30s tid=0x00007effb0219800 nid=0x1ed06 waiting on condition [0x00007eff96cf1000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(java.base@11.0.1/Native Method) at java.lang.Thread.sleep(java.base@11.0.1/Thread.java:339) at java.util.concurrent.TimeUnit.sleep(java.base@11.0.1/TimeUnit.java:446) at com.smart.ThreadDemo.run(Demo.java:11) - locked <0x00000000c51b5798> (a java.lang.Class for com.smart.ThreadDemo) at java.lang.Thread.run(java.base@11.0.1/Thread.java:834) Locked ownable synchronizers: - None
waiting to lock
"Thread-1" #12 prio=5 os_prio=0 cpu=1.60ms elapsed=14.30s tid=0x00007effb021b800 nid=0x1ed07 waiting for monitor entry [0x00007eff96bf0000] java.lang.Thread.State: BLOCKED (on object monitor) at com.smart.ThreadDemo.run(Demo.java:11) - waiting to lock <0x00000000c51b5798> (a java.lang.Class for com.smart.ThreadDemo) at java.lang.Thread.run(java.base@11.0.1/Thread.java:834) Locked ownable synchronizers: - None
waiting on
通过synchronized关键字,成功获取到了对象的锁后,调用wait方法,进入对象的等待区等待。在调用栈顶出现,线程状态为WAITING或TIMED_WATING。
"Thread-0" #11 prio=5 os_prio=0 cpu=0.15ms elapsed=15.14s tid=0x00007fc168215000 nid=0x22f8 in Object.wait() [0x00007fc16ce93000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(java.base@11.0.1/Native Method) - waiting on <0x00000000c51b5cb8> (a com.smart.ThreadDemo) at java.lang.Object.wait(java.base@11.0.1/Object.java:328) at com.smart.ThreadDemo.run(Demo.java:11) - waiting to re-lock in wait() <0x00000000c51b5cb8> (a com.smart.ThreadDemo) at java.lang.Thread.run(java.base@11.0.1/Thread.java:834)
parking to wait for
park是基本的线程阻塞原语,不通过监视器在对象上阻塞。
线程动作
线程状态产生的原因
runnable:状态一般为RUNNABLE in Object.wait():等待区等待,状态为WAITING或TIMED_WAITING waiting for monitor entry:进入区等待,状态为BLOCKED waiting on condition:等待区等待、被park sleeping:休眠的线程,调用Thread.sleep()。
Wait on condition 该状态出现在线程等待某个条件的发生。
具体是什么原因,可以结合 stacktrace来分析。 最常见的情况就是线程处于sleep状态,等待被唤醒。
常见的情况还有等待网络IO:在java引入nio之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。
在 NIO里采用新的机制,编写的服务器程序的性能和可扩展性都得到提高。 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 CPU时间,相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。
线程Dump的分析
原则
结合代码阅读的推理。需要线程Dump和源码的相互推导和印证。
造成Bug的根源往往会在调用栈上直接体现,一定格外注意线程当前调用之前的所有调用
进入区等待
"Thread-1" #12 prio=5 os_prio=0 cpu=1.60ms elapsed=14.30s tid=0x00007effb021b800 nid=0x1ed07 waiting for monitor entry [0x00007eff96bf0000] java.lang.Thread.State: BLOCKED (on object monitor) at com.smart.ThreadDemo.run(Demo.java:11) - waiting to lock <0x00000000c51b5798> (a java.lang.Class for com.smart.ThreadDemo) at java.lang.Thread.run(java.base@11.0.1/Thread.java:834)
线程状态BLOCKED,线程动作waiting for monitor entry,调用修饰waiting to lock总是一起出现。表示在代码级别已经存在冲突的调用。必然有问题的代码,需要尽可能减少其发生。
同步块阻塞
一个线程锁住某对象,大量其他线程在该对象上等待
"blocker" runnable java.lang.Thread.State: RUNNABLE at com.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.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.javadump.Blocker$2.run(Blocker.java:41) - waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
持续运行的IO IO操作是可以RUNNABLE状态达成阻塞。例如:数据库死锁、网络读写。 格外注意对IO线程的真实状态的分析。 一般来说,被捕捉到RUNNABLE的IO调用,都是有问题的。
入手点总结
wait on monitor entry: 被阻塞的,肯定有问题
runnable : 注意IO线程
in Object.wait(): 注意非线程池等待
使用

-l长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表
-e 打印线程的额外信息
死锁分析
死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
package com.smart; import java.util.concurrent.TimeUnit; class LockDemo{ private Object objLockOne=new Object(); private Object objLockTwo=new Object(); public void lockMethodOne(){ synchronized (objLockOne){ try { System.out.println(Thread.currentThread().getName()); TimeUnit.SECONDS.sleep(10); synchronized (objLockTwo){ } } catch (InterruptedException e) { e.printStackTrace(); } } } public void lockMethodTwo(){ synchronized (objLockTwo){ System.out.println(Thread.currentThread().getName()); synchronized (objLockOne){ } } } } class ThreadLock implements Runnable{ private LockDemo lock; private boolean flag; public ThreadLock(LockDemo demo,boolean flag){ this.lock=demo; this.flag=flag; } @Override public void run() { if(flag) lock.lockMethodOne(); else lock.lockMethodTwo(); } } public class DeadLock { public static void main(String[] args) throws InterruptedException { LockDemo lockDemo = new LockDemo(); ThreadLock threadLockOne = new ThreadLock(lockDemo, true); new Thread(threadLockOne).start(); TimeUnit.MILLISECONDS.sleep(1); ThreadLock threadLockTwo = new ThreadLock(lockDemo, false); new Thread(threadLockTwo).start(); } }
使用jstack来看一下线程堆栈信息:
Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00000000575e7888 (object 0x00000000d5d9a7f0, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x00000000575ea278 (object 0x00000000d5d9a800, a java.lang.Object), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at com.smart.LockDemo.lockMethodTwo(DeadLock.java:28) - waiting to lock <0x00000000d5d9a7f0> (a java.lang.Object) - locked <0x00000000d5d9a800> (a java.lang.Object) at com.smart.ThreadLock.run(DeadLock.java:46) at java.lang.Thread.run(Thread.java:748) "Thread-0": at com.smart.LockDemo.lockMethodOne(DeadLock.java:16) - waiting to lock <0x00000000d5d9a800> (a java.lang.Object) - locked <0x00000000d5d9a7f0> (a java.lang.Object) at com.smart.ThreadLock.run(DeadLock.java:45) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.
参考:
https://www.hollischuang.com/archives/110
浙公网安备 33010602011771号