使用jstack排查JVM进程死锁
前言
在Linux系统使用JDK自带的jstack指令分析输出的线程信息排查死锁的详细步骤。
例子程序
下面是一个模拟线程死锁的例子程序,编译(javac DeadLockSample.java)后执行(java DeadLockSample)这个程序来启动一个JVM进程。
其中一个线程会成功获取到DeadLockSample的Class对象锁持续打印(locking...),由于是死循环,所以锁不会释放,其他两个意图获取锁的线程只能无限等待。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class DeadLockSample {
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
EXECUTOR.submit(DeadLockSample::doSomething);
EXECUTOR.submit(DeadLockSample::doSomething);
EXECUTOR.submit(DeadLockSample::doSomething);
}
/**
* 占用锁循环输出不释放
*/
private static synchronized void doSomething() {
try {
while (true) {
System.out.println("locking...");
TimeUnit.SECONDS.sleep(3);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
排查过程
找出Java进程ID
通过JDK自带的jps指令列出服务器上所有Java进程,找到我们排查死锁的进程ID。
找出等待相同锁的多个线程
执行jstack <Java进程ID>查看进程中所有线程运行信息。
jstack 8200
从输出的信息中我们可以看到有两个线程(线程名为pool-1-thread-2和pool-1-thread-3)在等待(waiting to lock)同一个类型(a java.lang.Class for DeadLockSample)对象(0x00000000f0c72bb8)的锁。
而持有(locked)这个类型(a java.lang.Class for DeadLockSample)对象(0x00000000f0c72bb8)锁的线程名为(pool-1-thread-1)。
到此我们已经找出了持有死锁的线程,关键在于锁肯定被一个线程持有,如果有线程在等待同一把锁,那么他们锁定(lock)和等待(waiting to lock)的对象类型(a java.lang.Class for DeadLockSample)和对象实例的十六进制唯一 标识(0x00000000f0c72bb8)是一样的。
其他快速查找方式
由于jstack输出的信息非常多,查找关键信息不是很方便,通过grep命令过滤只输出我们需要的信息可以更快的定位问题线程,格式为jstack <Java进程ID> | grep '<过滤关键字>'
- 找出等待锁的相关信息
jstack 8200 | grep 'waiting to lock'
- 找出所有持有锁的相关信息
jstack 8200 | grep 'locked'
- 将jstack结果输出到指定文件,格式为jstack [Java进程ID] > [文件名],> 代表覆盖,>> 代表追加。
jstack 8200 > 8200.text
# jstack 8200 >> 8200.text
最后根据十六进制唯一标识在文件中查找相关线程信息也可以定位问题。
总结
排查主要使用jstack命令输出线程运行相关信息,然后找出持有、等待锁的线程,并根据锁的对象实例的十六进制唯一标识来判断是否是通一把锁。
虽然知道了死锁的相关信息,但还需要根据线程名、对象的类型、线程运行状态、方法堆栈等信定位到代码内的具体位置。
其实有更快的方式是使用阿里的 Arthas,如果不方便或者没有这个工具包使用本文方式是比较合适的,因为指令JDK自带无需依赖其他工具。