作者:禹明明,叩丁狼高級講師。原創(chuàng)文章,轉(zhuǎn)載請注明出處。
看此文章前請先了解之前一篇文章 "Java死鎖之理解死鎖" 中的死鎖示例
我們在開發(fā)中應(yīng)該盡量避免死鎖,但是如果真的有死鎖產(chǎn)生那么我們怎么在一個(gè)復(fù)雜的項(xiàng)目中快速的找到死鎖產(chǎn)生的原因呢?
我大概總結(jié)了一下常用的幾種方式:
使用JConsole檢測死鎖
在我們的JAVA_HOME/bin目錄下我們可以找到一個(gè)jdk提供的虛擬機(jī)監(jiān)控工具叫Jconsole,雙擊打開連接你要查找的虛擬機(jī)進(jìn)程,如圖:

進(jìn)入監(jiān)控界面后點(diǎn)擊左下角的檢測死鎖會檢測出死鎖的線程,點(diǎn)擊具體的線程可以看到如下圖死鎖相關(guān)信息.

比如下圖所示,我們可以看到
名稱: B //發(fā)生死鎖的線程名稱
狀態(tài): java.lang.Object@6b0eb7ca上的BLOCKED, 擁有者: A //線程B正在等待的鎖, 被線程A所持有
總阻止數(shù): 1, 總等待數(shù): 1
堆棧跟蹤:
test.B.run(TestThread.java:56) //該死鎖發(fā)生在TestThread.java文件的第56行
- 已鎖定 java.lang.Object@6cf3ef8 //線程B持有的鎖
這是的發(fā)生死鎖的線程A的信息:
名稱: A
狀態(tài): java.lang.Object@6cf3ef8上的BLOCKED, 擁有者: B //線程A正在等待的鎖, 被線程B所持有
總阻止數(shù): 1, 總等待數(shù): 1
堆棧跟蹤:
test.A.run(TestThread.java:33)
- 已鎖定 java.lang.Object@6b0eb7ca //線程A持有的鎖
根據(jù)此信息我們可以快速定位到死鎖發(fā)生的位置和鎖對象,以及和其相關(guān)的線程從而解決問題。
使用JStack檢測死鎖
JStack 是JDK自帶的命令行工具,主要用于線程Dump分析。(Dump文件是進(jìn)程的內(nèi)存鏡像。保存的是進(jìn)程的執(zhí)行狀態(tài)信息),該工具可以在JAVA_HOME/bin目錄下找到,但是并非是圖形化界面,所以不能雙擊運(yùn)行
使用方式:
1.打開CMD命令提示符界面,輸入jps 命令查看Java進(jìn)程信息

2.找到要調(diào)試的JVM進(jìn)程號 pid ,這里是15964
執(zhí)行 jsatck -l pid (-l 參數(shù)可以打印出鎖的相關(guān)信息)
PS:如果是真實(shí)項(xiàng)目可能會列出很多信息, 我們可以使用 jstack -l pid > D:dead.txt命令將所有信息導(dǎo)入到dead.txt中 然后在UE等一些比較強(qiáng)悍的文本編輯器中進(jìn)行分析

信息比較多,我只截取最后這點(diǎn)跟死鎖相關(guān)的信息
通過此信息,我們也可以分析出死鎖發(fā)生的位置和相關(guān)線程,從而快速定位。
使用ThreadMXBean檢測
java 中提供了可以檢測死鎖的工具類ThreadMXBean,我們可以用它來在項(xiàng)目運(yùn)行時(shí)期使用代碼去檢測是否有死鎖存在.
下面這段代碼請參考"理解java線程死鎖"這篇文章,我們在其中添加了使用ThreadMXBean獲取死鎖信息的代碼
public class TestThread {
public static final Object lock1 = new Object();//鎖對象1
public static final Object lock2 = new Object();//鎖對象2
//獲取ThreadMXBean
public static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
//測試方法
public static void main(String[] args) throws InterruptedException {
//啟動(dòng)死鎖線程
new A("A").start();
new B("B").start();
//等待一段時(shí)間再執(zhí)行死鎖檢測
Thread.sleep(200);
//獲取到所有死鎖線程的id
long[] deadlockedThreads = mbean.findDeadlockedThreads();
//遍歷數(shù)組獲取所有的死鎖線程詳細(xì)堆棧信息并打印
for (long pid : deadlockedThreads) {
//此方法獲取不帶有堆棧跟蹤信息的線程數(shù)據(jù)
//hreadInfo threadInfo = mbean.getThreadInfo(pid);
//第二個(gè)參數(shù)指定轉(zhuǎn)儲多少項(xiàng)堆棧跟蹤信息,設(shè)置為Integer.MAX_VALUE可以轉(zhuǎn)儲所有的堆棧跟蹤信息
ThreadInfo threadInfo = mbean.getThreadInfo(pid,Integer.MAX_VALUE);
System.out.println(threadInfo);
}
}
}
打印出的死鎖線程信息如下: 其中 "B" , "A" 是我的死鎖示例中兩個(gè)線程的名字
"B" Id=14 BLOCKED on java.lang.Object@58d25a40 owned by "A" Id=13
at test.B.run(TestThread.java:70)
- blocked on java.lang.Object@58d25a40
"A" Id=13 BLOCKED on java.lang.Object@726f3b58 owned by "B" Id=14
at test.A.run(TestThread.java:47)
- blocked on java.lang.Object@726f3b58
PS:檢測死鎖和獲取堆棧信息是比較費(fèi)性能的操作,如非必要不要經(jīng)常執(zhí)行
