一篇文章搞清JVM死鎖問題及排查


關於死鎖,一直是面試和日常開發中的熟悉話題,本文將進行一下探討:

  1. 什么是死鎖
  2. 出現死鎖的原因
  3. 如何避免死鎖
  4. 代碼中死鎖問題怎么排查

@

1. 什么是死鎖

死鎖是指兩個或兩個以上的進程或線程,在執行過程中,由於競爭資源而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去

划重點:兩個或兩個以上進程或線程,競爭資源,最終阻塞無法進行下去

這里可能會問:外力作用是什么外力?資源剝奪;撤銷進程;進程回退等

2. 出現死鎖的原因

  1. 系統資源不足;

  2. 進程推進順序非法

  3. 系統資源分配不當。

    另外,信號量使用不當也會造成死鎖。比如A等B消息,B等A的消息

死鎖產生的有4個必要條件
(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。

3. 如何預防和避免死鎖

  • 預防:

死鎖的預防基本思想打破產生死鎖的四個必要條件中的一個或幾個,保證系統不會進入死鎖狀態。

比如:

打破互斥條件:允許進程同時訪問某些資源

打破不剝奪條件:允許進程從占有者占有的資源中強行剝奪一些資源

打破請求與保持條件:進程在運行前一次性地向系統申請它所需要的全部資源

打破循環等待條件:實行資源有序分配策略

  • 避免:
  1. 加鎖順序(線程按照一定的順序加鎖)
  2. 加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己占有的鎖)
  3. 死鎖檢測

阿里巴巴中最新的開發規約,里面有對避免死鎖的說明,具體如下:


【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。 說明:線程一需要對表 A、B、C 依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序也必須是 A、B、C,否則可能出現死鎖。

4. 實戰JVM死鎖問題排查

4.1 死鎖代碼案例

按照死鎖產生原則,可寫出一個產生死鎖的程序

public class DeadLock {
    //創建兩個對象,用兩個線程分別先后獨占
    private Boolean flag1 = true;
    private Boolean flag2 = false;

    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程1開始,作用是當flag1 = true 時,將flag2也改為 true");
                synchronized (deadLock.flag1){
                    if(deadLock.flag1){
                        try{
                            //睡眠1s ,模擬業務執行耗時,並保證兩個線程進入死鎖狀態
                            Thread.sleep(1000);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                        System.out.println("flag1 = true,准備鎖住flag2...");
                        synchronized (deadLock.flag2){
                            deadLock.flag2 = true;
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程2開始,作用是當flag2 = false 時,將flag1也改為 false");
                synchronized (deadLock.flag2){
                    if(!deadLock.flag2){
                        try{
                            //睡眠1s ,模擬業務執行耗時,並保證兩個線程進入死鎖狀態
                            Thread.sleep(1000);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                        System.out.println("flag2 = false,准備鎖住flag1...");
                        synchronized (deadLock.flag1){
                            deadLock.flag1 = false;
                        }

                    }
                }
            }
        }).start();
    }
}

以上代碼,可以用一個死鎖的圖解釋。線程1獨占對象1,想要訪問對象2,而對象2此時已經獨占對象2,在等待對象1的資源釋放,此時線程1因無法獲取到對象2而無法向下執行,因此沒法釋放對象1,線程2同理,造成了死鎖狀態,兩個線程都阻塞在等待資源處

4.2 死鎖問題JVM工具排查

4.2.1 jps+jstack方式排查

  1. 查找程序運行端口

    > jps -l 
    18714 sun.tools.jps.Jps
    18703 jvm.DeadLock
    
  2. jstack打印堆棧信息,發現死鎖存在的位置,進行排查

    > jstack -l 18703
    

4.2.2 jconsole方式排查

mac下:輸入jconsol命令通過可視化界面連接

image-20200612232936748

選擇線程,監測死鎖。會將死鎖的線程信息都展示出來

4.2.3 jvisualvm方式

用命令行召喚出jconsole,選擇對應進程即可直觀看到死鎖的存在


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM