淺談JVM - 內存結構(三)- 虛擬機棧

2.1 定義java

Java Virtual Machine Stacks(Java虛擬機棧)linux

  • Java 虛擬機棧描述的是 Java 方法執行的內存模型,用於存儲棧幀,是線程私有的,生命週期隨着線程啓動而產生,線程結束而消亡
  • 線程啓動時會建立虛擬機棧,每一個方法在執行時會在虛擬機棧中建立一個棧幀(Stack Frame),用於存儲局部變量表、操做數棧、動態鏈接、方法返回地址等信息。每一個方法從調用到執行完成的過程,就對應着一個棧幀在虛擬機棧中的入棧(壓棧)到出棧(彈棧)的過程
  • 每一個線程只能有一個活動棧幀,對應着正在執行的那個方法 問題辨析
  1. 垃圾回收是否涉及棧內存?shell

    不涉及。棧內存無非就是一次次的方法調用產生的棧幀內存,棧幀內存在每一次方法調用後都會被彈出棧,也就是這部份內存會被自動的回收掉,因此並不須要垃圾回收來回收棧內存。windows

  2. 棧內存分配越大越好嗎?數組

    不是。安全

    • 棧內存能夠在代碼運行時經過一個虛擬機參數來指定其大小 -Xss size。不指定的話,除了windows系統,默認都是1M,windows系統是依據虛擬內存大小分配。
    • 棧內存分配的越大,只是可以進行更屢次的方法遞歸調用,並不會增快運行的效率,反而會使得可執行的線程數變少(總內存不變,每一個線程的棧內存變大,數量變少)
  3. 方法內的局部變量是否線程安全?多線程

    變量是不是線程安全的,取決於這個變量被多線程共享時,每次運行結果和單線程運行的結果是不是同樣的。app

    • 若是方法內局部變量沒有逃離方法的做用訪問,它是線程安全的。
    • 若是局部變量是引用對象,且逃離了方法的做用範圍,那麼就須要考慮線程安全(基本數據類型不會有這個問題)

    示例代碼1jvm

    static void m1() {
      int x=0;
      for(int i=0;i<500;i++){
        x++;
      }
      System.out.println(x);
    }

    x這個局部變量是線程安全的。每一個線程對應一個棧,而後線程內每次方法調用都會產生一個新的棧幀,因此x變量處於不一樣線程的棧的棧幀中,互不影響,也就是線程安全的。工具

    示例代碼2

    public static void m1() {
      StringBuilder sb = new StringBuilder();
      sb.append(1);
      sb.append(2);
      sb.append(3);
      System.out.println(sb.toString());
    }
    
    public static void m2(StringBuilder sb) {
      sb.append(1);
      sb.append(2);
      sb.append(3);
      System.out.println(sb.toString());
    }
    
    public static StringBuilder m3() {
      StringBuilder sb = new StringBuilder();
      sb.append(1);
      sb.append(2);
      sb.append(3);
      return sb;
    }
    • m1方法中sb是線程安全的,由於其是線程內的局部變量。
    • m2方法中sb是線程不安全的,由於其是做爲方法的參數傳遞進來,那麼就有可能有其餘線程可以訪問到這個變量,那麼這個變量就是多個線程共享的,可能形成值不一致,也就是線程不安全的。
    • m3方法中sb是線程不安全的,雖然其是線程中的局部變量,可是其做爲返回值返回了,那麼就有可能被其餘線程使用,也就是多線程共享,可能形成值不一致,線程不安全。

2.2 棧幀

  • 棧幀存在於 Java 虛擬機棧中,是 Java 虛擬機棧中的單位元素,每一個線程中調用同一個方法或者不一樣的方法,都會建立不一樣的棧幀(能夠簡單理解爲,一個線程調用一個方法建立一個棧幀),因此,調用的方法鏈越多,建立的棧幀越多(例如:遞歸)。每調用一個新的方法,被調用方法對應的棧幀就會被放到棧頂(入棧),也就是成爲新的當前棧幀。當一個方法執行完成退出的時候,此方法對應的棧幀也相應銷燬(出棧)。

  • 2.2.1 局部變量表(Local

2.2.1 局部變量表(Local Variable Table)

  • 每一個棧幀中都包含一組稱爲局部變量表的變量列表,用於存放方法參數和方法內部定義的局部變量。在 Java 程序編譯成 Class 文件時,在 Class 文件格式屬性表中 Code 屬性的 max_locals(局部變量表所需的存儲空間,單位是 Slot) 數據項中肯定了須要分配的局部變量表的最大容量。

  • 局部變量表的容量以變量槽(Variable Slot)爲最小單位,不過 Java 虛擬機規範中並無明確規定每一個 Slot 所佔據的內存空間大小,只是有導向性地說明每一個 Slot 都應該存放的8種類型: byte、short、int、float、char、boolean、reference(對象引用就是存到這個棧幀中的局部變量表裏的,這裏的引用指的是局部變量的對象引用,而不是成員變量的引用。成員變量的對象引用是存儲在 Java 堆(Heap)中)、returnAddress(虛擬機數據類型,returnAddress 類型的值就是指向特定指令內存地址的指針,JVM支持多線程,每一個線程有本身的程序計數器(pc register),而 pc 中的值就是當前指令所在的內存地址,即 returnAddress 類型的數據,當線程執行 native 方法時,pc 中的值爲 undefined)類型的數據,這8種類型的數據,均可以使用32位或者更小的空間去存儲。Java 虛擬機規範容許 Slot 的長度能夠隨着處理器、操做系統或者虛擬機的不一樣而發生變化。對於64位的數據類型,虛擬機會以高位在前的方式爲其分配兩個連續的 Slot 空間。即 long 和 double 兩種類型。作法是將 long 和 double 類型速寫分割爲32位讀寫的作法。不過因爲局部變量表創建在線程的堆棧上,是線程的私有數據,不管讀寫兩個連續的 Slot 是不是原子操做,都不會引發數據安全問題。

  • Java 虛擬機經過索引定位的方式使用局部變量表,索引值的範圍是從0開始到局部變量表最大的 Slot 數量。若是是32位數據類型的數據,索引 n 就表示使用第 n 個 Slot,若是是64位數據類型的變量,則說明要使用第 n 和第 n+1 兩個 Slot。

  • 在方法執行過程當中,Java 虛擬機是使用局部變量表完成參數值到參數變量列表的傳遞過程。若是是實例方法(非 static 方法),那麼局部變量表中的第0位索引的 Slot 默認是用來傳遞方法所屬對象實例的引用,在方法中能夠經過關鍵字 this 來訪問這個隱含的參數。其他參數按照參數表的順序來排列,佔用從1開始的局部變量 Slot,參數表分配完畢後,再根據方法體內部定義的變量順序和做用域分配其他的 Slot。

  • 局部變量表中的 Slot 是可重用的,方法體中定義的變量,其做用域並不必定會覆蓋整個方法體,若是當前字節碼程序計數器的值已經超過了某個變量的做用域,那麼這個變量相應的 Slot 就能夠交給其餘變量去使用,節省棧空間,但也有可能會影響到系統的垃圾收集行爲。

  • 局部變量無初始值(實例變量和類變量都會被賦予初始值),類變量有兩次賦初始值的過程,一次在準備階段,賦予系統初始值;另一次在初始化階段,賦予開發者定義的值。所以即便在初始化階段開發者沒有爲類變量賦值也沒有關係,類變量仍然具備一個肯定的默認值。但局部變量就不同了,若是一個局部變量定義了但沒有賦初始值是不能使用的。

使用一段代碼說明一下局部變量表:

// java 代碼
public int test() {
    int x = 0;
    int y = 1;
    return x + y;
}

// javac 編譯後的字節碼,使用 javap -v 查看
public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iconst_1
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: ireturn
      LineNumberTable:
        line 7: 0
        line 8: 2
        line 9: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  this   Lcom/alibaba/uc/TestClass;
            2       6     1     x   I
            4       4     2     y   I

對應上面的解釋說明,經過 LocalVariableTable 也能夠看出來: Code 屬性: stack(int x(1個棧深度)+ int y(1個棧深度))=2, locals(this(1 Slot)+ int x(1 Slot)+ int y(1 Slot))=3, args_size(非 static 方法,this 隱含參數)=1

驗證 Slot 複用,運行如下代碼時,在 VM 參數中添加 -verbose:gc

public void test() {
   {
      byte[] placeholder = new byte[64 * 1024 * 1024];
   }
   int a = 0; // 當這段代碼註釋掉時,System.gc() 執行後,也並不會回收這64MB內存。當這段代碼執行時,內存被回收了
   System.gc();
}

局部變量表中的 Slot 是否還存在關於 placeholder 數組對象的引用。當 int a = 0; 不執行時,代碼雖然已經離開了 placeholder 的做用域,可是後續並無任何對局部變量表的讀寫操做,placeholder 本來所佔用的 Slot 尚未被其餘變量所複用,因此 placeholder 做爲 GC Roots(全部 Java 線程當前活躍的棧幀裏指向 Java 堆裏的對象的引用) 仍然是可達對象。當 int a = 0; 執行時,placeholder 的 Slot 被變量 a 複用,因此 GC 觸發時,placeholder 變成了不可達對象,便可被 GC 回收。

2.2.2 操做數棧(Operand Stack)

  • 操做數棧是一個後入先出(Last In First Out)棧,方法的執行操做在操做數棧中完成,每個字節碼指令往操做數棧進行寫入和提取的過程,就是入棧和出棧的過程。

  • 同局部變量表同樣,操做數棧的最大深度也是Java 程序編譯成 Class 文件時被寫入到 Class 文件格式屬性表的 Code 屬性的 max_stacks 數據項中。

  • 操做數棧的每個元素能夠是任意的 Java 數據類型,32位數據類型所佔的棧容量爲1,64位數據類型所佔的棧容量爲2,在方法執行的任什麼時候候,操做數棧的深度都不會超過在 max_stacks 數據項中設定的最大值(指的是進入操做數棧的 「同一批操做」 的數據類型的棧容量的和)。

  • 當一個方法剛剛執行的時候,這個方法的操做數棧是空的,在方法執行的過程當中,經過一些字節碼指令從局部變量表或者對象實例字段中複製常量或者變量值到操做數棧中,也提供一些指令向操做數棧中寫入和提取值,及結果入棧,也用於存放調用方法須要的參數及接受方法返回的結果。例如,整數加法的字節碼指令 iadd(使用 iadd 指令時,相加的兩個元素也必須是 int 型) 在運行的時候將操做數棧中最接近棧頂的兩個 int 數值元素出棧相加,而後將相加結果入棧。

2.2.3 動態鏈接(Dynamic Linking)

  • 每一個棧幀都包含一個指向運行時常量池(JVM 運行時數據區域)中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態連接。

  • 在 Class 文件格式的常量池(存儲字面量和符號引用)中存有大量的符號引用(1.類的全限定名,2.字段名和描述符,3.方法名和描述符),字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用一部分會在類加載過程的解析階段的時候轉化爲直接引用(指向目標的指針、相對偏移量或者是一個可以直接定位到目標的句柄),這種轉化稱爲靜態解析。另一部分將在每一次的運行期期間轉化爲直接引用,這部分稱爲動態鏈接。

    看看如下代碼的 Class 文件格式的常量池:

// java 代碼
 public Test test() {
    return new Test();
 }

// 字節碼指令
// Class文件的常量池
Constant pool:
   #1 = Methodref          #4.#19         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#20         // com/alibaba/uc/Test.i:I
   #3 = Class              #21            // com/alibaba/uc/Test
   #4 = Class              #22            // java/lang/Object
   #5 = Utf8               i
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/alibaba/uc/Test;
  #14 = Utf8               test
  #15 = Utf8               ()I
  #16 = Utf8               <clinit>
  #17 = Utf8               SourceFile
  #18 = Utf8               Test.java
  #19 = NameAndType        #7:#8          // "<init>":()V
  #20 = NameAndType        #5:#6          // i:I
  #21 = Utf8               com/alibaba/uc/Test
  #22 = Utf8               java/lang/Object

public int test();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: getstatic     #2                  // Field i:I
         3: areturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lcom/alibaba/uc/Test;

從上面字節碼指令看出 0: getstatic #2 // Field i:I 這行字節碼指令指向 Constant pool 中的 #2,而 #2 中指向了 #3 和 #20 爲符號引用,在類加載過程的解析階段會被轉化爲直接引用(指向方法區的指針)。

2.2.4 方法返回地址

  • 當一個方法開始執行後,只有兩種方式能夠退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的字節碼指令(例如:areturn),這時候可能會有返回值傳遞給上層的方法調用者(調用當前方法的方法稱爲調用者),是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法的方式稱爲正常完成出口(Normal Method Invocation Completion)
  • 另一種退出方式是,在方法執行過程當中遇到了異常,而且這個異常沒有在方法體內獲得處理,不管是Java虛擬機內部產生的異常,仍是代碼中使用 athrow 字節碼指令產生的異常,只要在本方法的異常處理器表中沒有搜索到匹配的異常處理器,就會致使方法退出,這種退出方法的方式稱爲異常完成出口(Abrupt Method Invocation Completion)。一個方法使用異常完成出口的方式退出,是不會給它的上層調用者產生任何返回值的。
  • 不管採用何種退出方式,在方法退出以後,都須要返回到方法被調用的位置,程序才能繼續執行,方法返回時可能須要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。通常來講,方法正常退出時,調用者的程序計數器的值能夠做爲返回地址,棧幀中極可能會保存這個計數器值。而方法異常退出時,返回地址是要經過異常處理器表來肯定的,棧幀中通常不會保存這部分信息。
  • 方法退出的過程實際上就等同於把當前棧幀出棧,所以退出時可能執行的操做有:恢復上層方法的局部變量表和操做數棧,把返回值(若是有的話)壓入調用者棧幀的操做數棧中,調整程序計數器的值以指向方法調用指令後面的一條指令等。

2.3 棧內存溢出

  • 棧幀過多致使棧內存溢出

    例如遞歸方法,當方法調用層級過多,產生大量的棧幀,卻沒有出棧,就會致使棧內存溢出,拋出StackOverflowError異常

    示例代碼

    public class Demo {
        private static int count;
    
        public static void main(String[] args) {
            try {
                method1();
            } catch (Throwable e) {
                e.printStackTrace();
                System.out.println(count);
            }
        }
    
        private static void method1() {
            count++;
            method1();
        }
    }

    執行結果

    java.lang.StackOverflowError
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    	at com.esell.Demo.method1(Demo.java:22)
    14602

    能夠看到方法總共執行了14602次就致使了棧溢出,能夠在虛擬機運行參數中設置-Xss128k調整棧內存大小,結果執行次數就會變小

  • 若是 Java 虛擬機棧能夠動態擴展,而且在嘗試擴展的時候沒法申請到足夠的內存,或者在建立新的線程時沒有足夠的內存去建立對應的虛擬機棧,那 Java 虛擬機將拋出一個 OutOfMemoryError 異常

2.4 線程運行診斷

2.4.1 cpu佔用太高

示例代碼

public class Demo {

    public static void main(String[] args) {
        new Thread(null, () -> {
            System.out.println("1...");
            while(true) {

            }
        }, "thread1").start();


        new Thread(null, () -> {
            System.out.println("2...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread2").start();

        new Thread(null, () -> {
            System.out.println("3...");
            try {
                Thread.sleep(1000000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread3").start();
    }
}

linux上運行此代碼

javac Demo.java
nohup java Demo &

查看輸出

tail -f nohup.out
1...
2...
3...
1...
2...
3...

查看cpu情況

top

能夠看到剛纔運行的java程序佔用cpu很高,進程號爲10526

查詢該進程下全部線程的運行狀態

top -Hp 10526

能夠看到佔用cpu最高的線程是10536

使用jstack工具獲取10526進程中全部線程運行信息

jstack 10526
2019-12-17 17:24:00
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.11-b03 mixed mode):

"Attach Listener" #12 daemon prio=9 os_prio=0 tid=0x00007f94d0001000 nid=0x294a waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #11 prio=5 os_prio=0 tid=0x00007f94f8008800 nid=0x291f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"thread3" #10 prio=5 os_prio=0 tid=0x00007f94f80e7000 nid=0x292a waiting on condition [0x00007f94fd9e3000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at Demo.lambda$main$2(Demo.java:24)
	at Demo$$Lambda$3/1523554304.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"thread2" #9 prio=5 os_prio=0 tid=0x00007f94f80e5000 nid=0x2929 waiting on condition [0x00007f94fdae4000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at Demo.lambda$main$1(Demo.java:15)
	at Demo$$Lambda$2/1072591677.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"thread1" #8 prio=5 os_prio=0 tid=0x00007f94f80e3800 nid=0x2928 runnable [0x00007f94fdbe5000]
   java.lang.Thread.State: RUNNABLE
	at Demo.lambda$main$0(Demo.java:6)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f94f80a8800 nid=0x2926 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f94f80a5800 nid=0x2925 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f94f80a3000 nid=0x2924 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f94f80a1000 nid=0x2923 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f94f8071800 nid=0x2922 in Object.wait() [0x00007f94fe1eb000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:142)
	- locked <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:158)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f94f806d800 nid=0x2921 in Object.wait() [0x00007f94fe2ec000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3521030> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)
	- locked <0x00000000e3521030> (a java.lang.ref.Reference$Lock)

"VM Thread" os_prio=0 tid=0x00007f94f8068800 nid=0x2920 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f94f80ad800 nid=0x2927 waiting on condition 

JNI global references: 152

其中thread1thread2thread3是咱們本身建立的線程,其餘都是jvm的線程

獲取佔用cpu最高的線程號的十六進制

printf '%x\n' 10536
2928

匹配jstack獲得的線程信息

"thread1" #8 prio=5 os_prio=0 tid=0x00007f94f80e3800 nid=0x2928 runnable [0x00007f94fdbe5000]
   java.lang.Thread.State: RUNNABLE
	at Demo.lambda$main$0(Demo.java:6)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

最後,匹配代碼的第6行,得出佔用cpu太高的緣由的在線程中無限循環執行致使。

2.4.2 程序執行很長時間沒有結果

示例代碼

public class Demo {
    static A a = new A();
    static B b = new B();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (a) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("我得到了 a 和 b");
                }
            }
        }).start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (b) {
                synchronized (a) {
                    System.out.println("我得到了 a 和 b");
                }
            }
        }).start();
    }
}

class A {

}

class B {
    
}

linux運行此代碼

javac Demo.java
nohup java Demo &

進程號爲10633,查看輸出時,發現一直沒有反應

根據進程號獲取全部線程信息

jstack 10633
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.11-b03 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007fc3a8001000 nid=0x299f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM" #10 prio=5 os_prio=0 tid=0x00007fc3d0008800 nid=0x298a waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" #9 prio=5 os_prio=0 tid=0x00007fc3d00dd800 nid=0x2994 waiting for monitor entry [0x00007fc3ad8d2000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at Demo.lambda$main$1(Demo.java:21)
	- waiting to lock <0x00000000e3539580> (a A)
	- locked <0x00000000e3539590> (a B)
	at Demo$$Lambda$2/1072591677.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"Thread-0" #8 prio=5 os_prio=0 tid=0x00007fc3d00db800 nid=0x2993 waiting for monitor entry [0x00007fc3ad9d3000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at Demo.lambda$main$0(Demo.java:13)
	- waiting to lock <0x00000000e3539590> (a B)
	- locked <0x00000000e3539580> (a A)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fc3d00a8800 nid=0x2991 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fc3d00a5800 nid=0x2990 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fc3d00a3000 nid=0x298f waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fc3d00a1000 nid=0x298e runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fc3d0071800 nid=0x298d in Object.wait() [0x00007fc3c05fc000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:142)
	- locked <0x00000000e3520e78> (a java.lang.ref.ReferenceQueue$Lock)
	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:158)
	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fc3d006d800 nid=0x298c in Object.wait() [0x00007fc3c06fd000]
   java.lang.Thread.State: WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	- waiting on <0x00000000e3521030> (a java.lang.ref.Reference$Lock)
	at java.lang.Object.wait(Object.java:502)
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)
	- locked <0x00000000e3521030> (a java.lang.ref.Reference$Lock)

"VM Thread" os_prio=0 tid=0x00007fc3d0068800 nid=0x298b runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007fc3d00ad800 nid=0x2992 waiting on condition 

JNI global references: 151


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007fc3b4003778 (object 0x00000000e3539580, a A),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007fc3b40062c8 (object 0x00000000e3539590, a B),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
	at Demo.lambda$main$1(Demo.java:21)
	- waiting to lock <0x00000000e3539580> (a A)
	- locked <0x00000000e3539590> (a B)
	at Demo$$Lambda$2/1072591677.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)
"Thread-0":
	at Demo.lambda$main$0(Demo.java:13)
	- waiting to lock <0x00000000e3539590> (a B)
	- locked <0x00000000e3539580> (a A)
	at Demo$$Lambda$1/640070680.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

Found one Java-level deadlock能夠在底部看到這句話,意思是死鎖,根據後面具體信息可知,在代碼29行,Thread-1鎖住對象b,在等待對象a的鎖,而在代碼13行,Thread-0鎖住對象a,在等待對象b的鎖,從而形成死鎖,程序無反應。

歡迎關注公衆號,後續文章更新通知,一塊兒討論技術問題 。

公衆號二維碼

相關文章
相關標籤/搜索