使用jstack观察虚拟机栈情况
jstack
(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照,查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合.
生成线程快照的目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。线程出现停顿的时候通过jstack
来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者在等待些什么资源。
以检查线程死锁为例:
public class Main_Work_2_1 {
public static void main(String[] args) throws InterruptedException {
Map<String, String> resource1 = Map.of("name", "resource1");
Map<String, String> resource2 = Map.of("name", "resource2");
Thread thread1 = new Thread(() -> runOverride(resource1, resource2), "线程1");
Thread thread2 = new Thread(() -> runOverride(resource2, resource1), "线程2");
thread1.start();
thread2.start();
}
/**
* run方法运行实例,主要是对先获得哪个锁做限制
* @param first
* @param second
*/
public static void runOverride(Map<String, String> first, Map<String, String> second) {
Thread threadName = Thread.currentThread();
synchronized (first) {
System.out.println(threadName + " get " + first.get("name"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " waiting get" + second.get("name"));
synchronized (second) {
System.out.println(threadName + " get " + second.get("name"));
}
}
}
}
运行输出:
Thread[线程1,5,main] get resource1
Thread[线程2,5,main] get resource2
Thread[线程2,5,main] waiting getresource1
Thread[线程1,5,main] waiting getresource2
代码分析:线程1 通过 synchronized (first)
获得resource1
的监视器锁,然后通过Thread.sleep(1000);
让线程1 休眠 1s;为的是让线程 2得到执行,然后获取到 resource2
的监视器锁。线程 1 和线程 2休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。
使用jstack进行栈情况分析:
首先使用jps
获取目标进程,可以看出Main_Work_2的进程pid为10566;
jps
然后查看对应进程栈内容:
jstack 10566
运行结果部分截图如下:
图一可以看出线程1处于BLOCKED
状态,- waiting to lock <0x000000008df738d0>
表示正在等待锁<0x000000008df738d0>
,并且- locked <0x000000008df73878>
表示正持有锁<0x000000008df73878>
;线程2也处于BLOCKED
状态,- waiting to lock <0x000000008df73878>
表示正在等待锁<0x000000008df73878>
,并且- locked <0x000000008df738d0>
表示正持有锁<0x000000008df738d0>
。
图二可以看出找到一个死锁,并且可以很明显的知道线程1在等待一个锁,该锁已被线程2获取;线程2也在等待一个锁,该锁已被线程1获取。
如果将死锁代码修改成正常代码后,在进程正常运行时,再一次使用stack查看的话,就会发现运行结果正常了。
也可以使用一下命令将结果输出到文件当中,方便记录和传阅。
jstack pid > jstackOut.txt
jstack命令
命令格式
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@] remote-hostname-or-IP
常用参数说明
1)options:
executable:Java executable from which the core dump was produced.(可能是产生core dump的java可执行程序)
core:将被打印信息的core dump文件
remote-hostname-or-IP:远程debug服务的主机名或ip
server-id:唯一id,假如一台主机上多个远程debug服务
2)基本参数:
-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:打印帮助信息
3)pid
pid 需要被打印配置信息的java进程id,可以用jps查询.
线程状态
想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:
NEW:未启动的。不会出现在Dump中。
RUNNABLE:在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
BLOCKED:受阻塞并等待监视器锁,被某个锁(synchronizers)给block住了。
WATING:无限期等待另一个线程执行特定操作;等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
TIMED_WATING:有时限的等待另一个线程的特定操作;和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。
TERMINATED:已退出的。
调用修饰
表示线程在方法调用时,额外的重要的操作。线程Dump分析的重要信息,修饰上方的方法调用。
locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者。
waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在进入区等待。
waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放后在等待区等待。
parking to wait for <地址> 目标:park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包会出现的新的机制,与synchronized体系不同。
线程动作
线程状态产生的原因
runnable: 状态一般为RUNNABLE。
in Object.wait(): 等待区等待,状态为WAITING或TIMED_WAITING。
waiting for monitor entry: 进入区等待,状态为BLOCKED。
waiting on condition: 等待区等待、被park。
sleeping: 休眠的线程,调用了Thread.sleep()。