try catch finally異常機制Java源碼+JVM字節碼(內含小彩蛋)

異常分爲:

Exception : 運行時異常,我們需要捕獲並且處理,讓程序自己恢復正常運行。
Error : 編譯時異常(程序本身無法處理的異常:系統崩潰,虛擬機報錯,內存不足,內存溢出)。

直接上代碼:
在這裏插入圖片描述
這裏看到可以catch到的是Exception異常。有一些編譯時可能會發生的異常也會提示我們throws拋出或者捕獲,否則無法通過編譯。
一步步來,先看看Exception和Error到底是個什麼東西。

Exception:發現繼承自Throwable

在這裏插入圖片描述

Error:(也是繼承自Throwable)

在這裏插入圖片描述

所以Error和Exception兩個異常類都繼承自Throwable。這裏借網上一張關係圖:

在這裏插入圖片描述

知道了這些基本概念後,開始看編譯時的字節碼文件長個什麼樣,其中還包含了很多意想不到的小驚喜。

先編譯並且查看字節碼文件

在這裏插入圖片描述

其他操作指令介紹在我另外一篇博客,這裏直奔主題!

彩蛋1:

在這裏插入圖片描述

這裏有個很有趣的地方,明明代碼裏面只有一個finally,爲什麼這裏有三個finally???那就是一個面試必問點,finally是異常機制中必定執行的語句塊,在JVM字節碼指令中爲了確保它一定被執行,所有地方都會加一段這個finally要執行的代碼。

(1)第一個地方,假設沒有異常,正常執行後也執行finally裏面的輸出
(2)第二個地方。有異常,捕獲處理完後,也執行finally裏面的輸出
(3)第三個地方。有異常並且沒有捕獲到,因爲我們定義的運行時異常很多,如果指定的Type類型不對應catch到的,或者是Throwable父類一些特殊異常,那就沒辦法正確處理,所以這一個finally都是用來捕獲漏網之魚的。

所以!finally在字節碼中有三份!

彩蛋2:

改一下代碼!

在這裏插入圖片描述
在這裏插入圖片描述

結果爲什麼是10086呢?不是try中沒有異常,返回了a = 1 嗎?難道是多個return 覆蓋了? 其實根據上面的分析,finally是一定會執行的,所以在第一次return 1 的時候,它沒有真正返回,只是進行了一個附值操作而已,直到finally重新修改了 a = 10086 ,再return。下面看字節碼:

{
  public com.company.Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class com/company/Main$Father
         3: dup
         4: invokespecial #3                  // Method com/company/Main$Father."<init>":()V
         7: astore_1
         8: iconst_0
         9: istore_2
        10: aload_1
        11: iload_2
        12: invokevirtual #4                  // Method com/company/Main$Father.get:(I)I
        15: istore_3
        16: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        19: iload_3
        20: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        23: return
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 10
        line 9: 16
        line 10: 23
}

這裏也就是說,finally沒有執行,這個方法都沒有彈出棧,所以不停地iload只是單純壓入操作數棧表,finally執行完了之後,才istore_3,pop出棧。同時有個奇怪的地方,就是異常athrow操作指令消失了!
在這裏插入圖片描述
在這裏插入圖片描述

這也是小彩蛋,正常來說我們catch到的Exception error,在這一步處理的時候,如果finally有return,那麼會發生吞噬異常的情況,也就是拋出的異常不見了。

彩蛋3:

這裏的異常,在字節碼中都是有一個ExceptionTable存放的。很簡單明瞭易懂:
再改一下代碼,讓它catch多個異常類型:

在這裏插入圖片描述

直奔主題看字節碼文件中的異常表:

在這裏插入圖片描述

解釋:

From : 從第幾行開始檢測
To :到第幾行結束
Target : 假如From和To中的代碼執行發生異常跳到哪一行處理
Type : 就是我剛纔定義的全部異常類型。
Type 爲 any 的情況,就是拋出了捕獲不到的類型。所以全部跳到71行去處理。類似於現在爆發的從未出現過的新型冠狀病毒。
71行就是我們的finally語句塊。這裏是最後一道防線,打印出來給程序員處理。同時讓程序繼續運行。

彩蛋4:

小的異常要放在前面,大的異常類放在後面。不然編譯不會通過。這樣是爲了防止大炮打蚊子的浪費。

在這裏插入圖片描述

以上就是個人的理解和總結,如果有不對的地方歡迎大家一起討論,謝謝大家,鞠躬!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章