共计 1788 个字符,预计需要花费 5 分钟才能阅读完成。
前言
在 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 自带无需依赖其余工具。