JVM?jstack實(shí)戰(zhàn)之死鎖問題詳解
如果在生產(chǎn)環(huán)境發(fā)生了死鎖,我們將看到的是部署的程序沒有任何反應(yīng)了,這個(gè)時(shí)候我們可以借助 jstack進(jìn)行分析,下面我們實(shí)戰(zhàn)操作查找死鎖的原因。所謂死鎖指的是是一組互相競(jìng)爭資源的線程因互相等待導(dǎo)致“永久”阻塞的現(xiàn)象。
構(gòu)造死鎖
編寫代碼,啟動(dòng)2個(gè)線程,Thread1拿到了obj1鎖,準(zhǔn)備去拿obj2鎖時(shí),obj2已經(jīng)被Thread2鎖定,所以發(fā)送了死鎖。
public class TestDeadLock { private static Object obj1 = new Object(); private static Object obj2 = new Object(); public static void main(String[] args) { new Thread(new Thread1()).start(); new Thread(new Thread2()).start(); } private static class Thread1 implements Runnable{ @Override public void run() { synchronized (obj1){ System.out.println("Thread1 拿到了 obj1 的鎖!"); try { // 停頓2秒的意義在于,讓Thread2線程拿到obj2的鎖 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2){ System.out.println("Thread1 拿到了 obj2 的鎖!"); } } } } private static class Thread2 implements Runnable{ @Override public void run() { synchronized (obj2){ System.out.println("Thread2 拿到了 obj2 的鎖!"); try { // 停頓2秒的意義在于,讓Thread1線程拿到obj1的鎖 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1){ System.out.println("Thread2 拿到了 obj1 的鎖!"); } } } } }
在idea運(yùn)行
#運(yùn)行結(jié)果
E:\Java\jdk8u171\bin\java.exe "-javaagent:C:\idea\IntelliJ IDEA 2019.3.2\lib\idea_rt.jar=50268:C:\idea\IntelliJ IDEA 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\Java\jdk8u171\jre\lib\charsets.jar;E:\Java\jdk8u171\jre\lib\deploy.jar;E:\Java\jdk8u171\jre\lib\ext\access-bridge-64.jar;E:\Java\jdk8u171\jre\lib\ext\cldrdata.jar;E:\Java\jdk8u171\jre\lib\ext\dnsns.jar;E:\Java\jdk8u171\jre\lib\ext\jaccess.jar;E:\Java\jdk8u171\jre\lib\ext\jfxrt.jar;E:\Java\jdk8u171\jre\lib\ext\localedata.jar;E:\Java\jdk8u171\jre\lib\ext\nashorn.jar;E:\Java\jdk8u171\jre\lib\ext\sunec.jar;E:\Java\jdk8u171\jre\lib\ext\sunjce_provider.jar;E:\Java\jdk8u171\jre\lib\ext\sunmscapi.jar;E:\Java\jdk8u171\jre\lib\ext\sunpkcs11.jar;E:\Java\jdk8u171\jre\lib\ext\zipfs.jar;E:\Java\jdk8u171\jre\lib\javaws.jar;E:\Java\jdk8u171\jre\lib\jce.jar;E:\Java\jdk8u171\jre\lib\jfr.jar;E:\Java\jdk8u171\jre\lib\jfxswt.jar;E:\Java\jdk8u171\jre\lib\jsse.jar;E:\Java\jdk8u171\jre\lib\management-agent.jar;E:\Java\jdk8u171\jre\lib\plugin.jar;E:\Java\jdk8u171\jre\lib\resources.jar;E:\Java\jdk8u171\jre\lib\rt.jar;E:\jvm-test\target\classes cn.zjq.jvm.TestDeadLock
Thread1 拿到了 obj1 的鎖!
Thread2 拿到了 obj2 的鎖!
#這里發(fā)生了死鎖,程序一直將等待下去
使用jstack進(jìn)行分析
C:\Users\dell>jps
12624 Launcher
5140 org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
7204
17000 Jps
18536 KotlinCompileDaemon
17196 TestDeadLock
29916 RemoteMavenServer
C:\Users\dell> jstack 17196 2021-07-25 21:52:25 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode): "DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x00000000029f2800 nid=0x1868 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Thread-1" #13 prio=5 os_prio=0 tid=0x000000001e5d3000 nid=0x6994 waiting for monitor entry [0x000000001f49f000] java.lang.Thread.State: BLOCKED (on object monitor) at cn.zjq.jvm.TestDeadLock$Thread2.run(TestDeadLock.java:49) - waiting to lock <0x000000076b688680> (a java.lang.Object) - locked <0x000000076b688690> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) "Thread-0" #12 prio=5 os_prio=0 tid=0x000000001e5d2000 nid=0x21d8 waiting for monitor entry [0x000000001f39f000] java.lang.Thread.State: BLOCKED (on object monitor) at cn.zjq.jvm.TestDeadLock$Thread1.run(TestDeadLock.java:29) - waiting to lock <0x000000076b688690> (a java.lang.Object) - locked <0x000000076b688680> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) "Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001e5aa800 nid=0x6e40 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001e516000 nid=0x1828 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001e505000 nid=0x712c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001e501000 nid=0x7f5c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001e4ed000 nid=0x2dc0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001e4ff800 nid=0x5040 runnable [0x000000001ec9e000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) - locked <0x000000076b7d0200> (a java.io.InputStreamReader) at java.io.InputStreamReader.read(InputStreamReader.java:184) at java.io.BufferedReader.fill(BufferedReader.java:161) at java.io.BufferedReader.readLine(BufferedReader.java:324) - locked <0x000000076b7d0200> (a java.io.InputStreamReader) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64) "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001e45b800 nid=0x49d8 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001e445800 nid=0x6b90 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001e441800 nid=0x2c0c in Object.wait() [0x000000001e91e000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076b508ed0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x000000076b508ed0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:212) "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001c54d000 nid=0x6508 in Object.wait() [0x000000001e41f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000076b506bf8> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x000000076b506bf8> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) "VM Thread" os_prio=2 tid=0x000000001c549000 nid=0x5af4 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002a08800 nid=0x347c runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002a0a000 nid=0x6bc8 runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002a0b800 nid=0x7d20 runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002a0d000 nid=0x728c runnable "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002a0f800 nid=0x595c runnable "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002a10800 nid=0x43fc runnable "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002a14800 nid=0x3c50 runnable "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002a16000 nid=0x364c runnable "VM Periodic Task Thread" os_prio=2 tid=0x000000001e5ca000 nid=0x1da4 waiting on condition JNI global references: 12 Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x000000001c5535f8 (object 0x000000076b688680, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x000000001c550d68 (object 0x000000076b688690, a java.lang.Object), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at cn.zjq.jvm.TestDeadLock$Thread2.run(TestDeadLock.java:49) - waiting to lock <0x000000076b688680> (a java.lang.Object) - locked <0x000000076b688690> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) "Thread-0": at cn.zjq.jvm.TestDeadLock$Thread1.run(TestDeadLock.java:29) - waiting to lock <0x000000076b688690> (a java.lang.Object) - locked <0x000000076b688680> (a java.lang.Object) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.
在輸出的信息中,已經(jīng)看到,發(fā)現(xiàn)了1個(gè)死鎖,關(guān)鍵看這個(gè):
可以清晰的看到:
Thread2獲取了<0x000000076b688690> 的鎖,等待獲取<0x000000076b688680>這個(gè)鎖
Thread1獲取了<0x000000076b688680> 的鎖,等待獲取<0x000000076b688690>這個(gè)鎖
由此可見,發(fā)生了死鎖。
怎么避免死鎖
死鎖產(chǎn)生的四個(gè)必要條件
互斥使用,即當(dāng)資源被一個(gè)線程使用(占有)時(shí),別的線程不能使用
不可搶占,資源請(qǐng)求者不能強(qiáng)制從資源占有者手中奪取資源,資源只能由資源占有者主動(dòng)釋放。
請(qǐng)求和保持,即當(dāng)資源請(qǐng)求者在請(qǐng)求其他的資源的同時(shí)保持對(duì)原有資源的占有。
循環(huán)等待,即存在一個(gè)等待隊(duì)列:T1等待占有T2的資源,T2等待占有T3的資源,T3等待占有T1的資源。這樣就形成了一個(gè)等待環(huán)路。
死鎖產(chǎn)生的原因
系統(tǒng)資源的競(jìng)爭:通常系統(tǒng)中擁有的不可剝奪資源,其數(shù)量不足以滿足多個(gè)進(jìn)程運(yùn)行的需要,使得進(jìn)程在運(yùn)行過程中,會(huì)因爭奪資源而陷入僵局。只有對(duì)不可剝奪資源的競(jìng)爭 才可能產(chǎn)生死鎖,對(duì)可剝奪資源的競(jìng)爭是不會(huì)引起死鎖的。
進(jìn)程推進(jìn)順序非法:進(jìn)程在運(yùn)行過程中,請(qǐng)求和釋放資源的順序不當(dāng),也同樣會(huì)導(dǎo)致死鎖。
信號(hào)量使用不當(dāng)也會(huì)造成死鎖:進(jìn)程間彼此相互等待對(duì)方發(fā)來的消息,結(jié)果也會(huì)使得這些進(jìn)程間無法繼續(xù)向前推進(jìn)。
死鎖產(chǎn)生的四個(gè)必要條件
如何避免死鎖呢
既然發(fā)生死鎖的原因是需要同時(shí)滿足四個(gè)必要條件,我們只需要打破其中任意一個(gè)條件即可避免死鎖問題。
而在這四個(gè)條件中第一個(gè)互斥條件是無法被破壞的,因?yàn)殒i本身就是通過互斥來解決線程安全問題的。
所以對(duì)于剩下三個(gè)我們可以逐一進(jìn)行分析
1.加鎖時(shí)限
對(duì)于不可搶占這個(gè)條件占用部分資源的線程進(jìn)一步申請(qǐng)其他資源時(shí),如果申請(qǐng)不到可以主動(dòng)釋放它占有的資源,這樣不可搶占這個(gè)條件就破壞掉了。線程嘗試獲取鎖的時(shí)候加上一定的時(shí)限,超過時(shí)限則放棄對(duì)該鎖的請(qǐng)求,并釋放自己占有的鎖。
2.一次性獲取所有鎖資源
我們可以一次性申請(qǐng)所有的鎖資源,這樣就不存在請(qǐng)求和保持條件了
3.加鎖順序
當(dāng)多個(gè)線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發(fā)生。對(duì)于循環(huán)等待這個(gè)條件
可以靠按序申請(qǐng)資源來進(jìn)行預(yù)防。
如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生。
如果一個(gè)線程(比如線程3)需要一些鎖,那么它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之后,才能獲取后面的鎖。
例如,線程2和線程3只有在獲取了鎖A之后才能嘗試獲取鎖C(譯者注:獲取鎖A是獲取鎖C的必要條件)。因?yàn)榫€程1已經(jīng)擁有了鎖A,所以線程2和3需要一直等到鎖A被釋放。然后在它們嘗試對(duì)B或C加鎖之前,必須成功地對(duì)A加了鎖。
按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制。但是,這種方式需要你事先知道所有可能會(huì)用到的鎖(譯者注:并對(duì)這些鎖做適當(dāng)?shù)呐判?,但總有些時(shí)候是無法預(yù)知的。
到此這篇關(guān)于JVM jstack實(shí)戰(zhàn)之死鎖問題詳解的文章就介紹到這了,更多相關(guān)JVM jstack死鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot項(xiàng)目的測(cè)試類實(shí)例解析
這篇文章主要介紹了SpringBoot項(xiàng)目的測(cè)試類實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12MyBatis實(shí)戰(zhàn)之Mapper注解的示例
本文主要介紹了MyBatis實(shí)戰(zhàn)之Mapper注解的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-10-10Java實(shí)現(xiàn)的生成二維碼統(tǒng)計(jì)掃描次數(shù)并轉(zhuǎn)發(fā)到某個(gè)地址功能詳解
這篇文章主要介紹了Java實(shí)現(xiàn)的生成二維碼統(tǒng)計(jì)掃描次數(shù)并轉(zhuǎn)發(fā)到某個(gè)地址功能,可實(shí)現(xiàn)生成帶統(tǒng)計(jì)功能的二維碼,涉及java二維碼的生成、參數(shù)傳遞、解析等相關(guān)操作技巧,需要的朋友可以參考下2018-07-07jmeter添加自定函數(shù)的實(shí)例(jmeter5.3+IntelliJ IDEA)
這篇文章主要介紹了jmeter添加自定函數(shù)的實(shí)例(jmeter5.3+IntelliJ IDEA),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11詳解Spring Boot 項(xiàng)目部署到heroku爬坑
這篇文章主要介紹了詳解Spring Boot 項(xiàng)目部署到heroku爬坑,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08Java AES加密解密的簡單實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄狫ava AES加密解密的簡單實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06Java構(gòu)造代碼塊,靜態(tài)代碼塊原理與用法實(shí)例分析
這篇文章主要介紹了Java構(gòu)造代碼塊,靜態(tài)代碼塊,結(jié)合實(shí)例形式分析了Java構(gòu)造代碼塊,靜態(tài)代碼塊的功能、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04