【JVM虛擬機】(9)-- JVM是如何處理異常的


JVM是如何處理異常的

上篇博客我們簡單說過異常信息是存放在屬性表集合中的Code屬性表里,那么這篇博客就單獨講Code屬性表中的exception_table。

在講之前我們先思考兩個問題?

1、為什么捕獲異常會較大的性能消耗?

2、為什么finally中的代碼會永遠執行?

接下來會從JVM虛擬機的角度來解答這兩個問題。

一、概念

1、JVM是如何捕獲異常的?

1、編譯而成的字節碼中,每個方法都附帶一個異常表
2、異常表中每一個條目代表一個異常處理器
3、觸發異常時,JVM會遍歷異常表,比較觸發異常的字節碼的索引值是否在異常處理器的from指針到to指針的范圍內。
4、范圍匹配后,會去比較異常類型和異常處理器中的type是否相同
5、類型匹配后,會跳轉到target指針所指向的字節碼(catch代碼塊的開始位置)
6、如果沒有匹配到異常處理器,會彈出當前方法對應的Java棧幀,並對調用者重復上述操作。

2、什么是異常表?

1. 每個方法都附帶一個異常表

2. 異常表中每一個條目, 就是一個異常處理器

異常表如下:

3、什么是異常處理器?其組成部分有哪些?

1、異常處理器由from指針、to指針、target指針,以及所捕獲的異常類型所構成(type)。
2、這些指針的數值就是字節碼的索引(bytecode index, bci),可以直接去定位字節碼。
3、from指針和to指針,標識了該異常處理器所監控的返回
4、target指針,指向異常處理器的起始位置。如catch代碼塊的起始位置
5、type:捕獲的異常類型,如Exception

4、如果在方法的異常表中沒有匹配到異常處理器,會怎么樣?

1、會彈出當前方法對應的Java棧幀
2、在調用者上重復異常匹配的流程。
3、最壞情況下,JVM需要編譯當前線程Java棧上所有方法的異常表

二、代碼演示

1、try-catch

public static void main(String[] args) {
  try {
    mayThrowException();
  } catch (Exception e) {
    e.printStackTrace();
  }
}
// 對應的 Java 字節碼
public static void main(java.lang.String[]);
  Code:
    0: invokestatic mayThrowException:()V
    3: goto 11
    6: astore_1
    7: aload_1
    8: invokevirtual java.lang.Exception.printStackTrace
   11: return
  Exception table:
    from  to target type
      0   3   6  Class java/lang/Exception  // 異常表條目

上面Code中的字節碼自己也沒有仔細研究過,我們可以具體看下面的Exception table表,來進行分析。

1、from和to: 指是try和catch之間的代碼的索引位置。from=0,to=3,代表從字節索引0的位置到3(不包括3)。

2、target : 代表catch后代碼運行的起始位置。

3、type : 指的是異常類型,這里是指Exception異常。

當程序觸發異常時,java虛擬機會從上至下遍歷異常表中的所有條目。當觸發異常的字節碼的索引值在某個異常表條目的監控范圍內,Java 虛擬機會判斷所拋出的異常

和該條目想要捕獲的異常是否匹配。如果匹配,Java 虛擬機會將控制流轉移至該條目target 指針指向的字節碼。

如果遍歷完所有異常表條目,Java 虛擬機仍未匹配到異常處理器,那么它會彈出當前方法對應的Java 棧幀,並且在調用者(caller)中重復上述操作。在最壞情況下,

Java 虛擬機需要遍歷當前線程 Java 棧上所有方法的異常表。

2、try-catch-finally

finally 代碼塊的編譯比較復雜。當前版本 Java 編譯器的做法,是復制 finally 代碼塊的內容,分別放在 try-catch 代碼塊所有正常執行路徑以及異常執行路徑的出口中

代碼示例

public static void XiaoXiao() {
   try {
       dada();
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       System.out.println("Finally");
   }
}
//通過javap 反編譯
public static void XiaoXiao();
    Code:
       0: invokestatic  #3                  // Method dada:()V
       3: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: ldc           #7                  // String Finally
       8: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: goto          41
      14: astore_0
      15: aload_0
      16: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: ldc           #7                  // String Finally
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: goto          41
      30: astore_1
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: ldc           #7                  // String Finally
      36: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: aload_1
      40: athrow
      41: return
    Exception table:
       from    to  target type
           0     3    14   Class java/lang/Exception
           0     3    30   any
          14    19    30   any

和之前有所不同,這次

1、異常表中,有三條數據,而我們僅僅捕獲了一個Exception
2、異常表的后兩個item的type為any

上面的三條異常表item的意思為

1、如果0到3之間,發生了Exception類型的異常,調用14位置的異常處理者。

2、 如果0到3之間,無論發生什么異常,都調用30位置的處理者。

3、 如果14到19之間(即catch部分),不論發生什么異常,都調用30位置的處理者。


`問題`:通過上面那幅圖和javap反編譯代碼就可以很好的解釋為什么finally里面的代碼永遠會執行?

原因:因為當前版本Java編譯器的做法,是復制finally代碼塊的內容,分別放到所有正常執行路徑,以及異常執行路徑的出口中

這三份finally代碼塊都放在什么位置:

第一份位於try代碼后 : 若果try中代碼正常執行,沒有異常那么finally代碼就在這里執行。
第二份位於catch代碼后 : 如果try中有異常同時被catch捕獲,那么finally代碼就在這里執行。
第三份位於異常執行路徑 : 如果如果try中有異常但沒有被catch捕獲,或者catch又拋異常,那么就執行最終的finally代碼。


問題 :為什么捕獲異常會較大的性能消耗?

因為構造異常的實例比較耗性能。這從代碼層面很難理解,不過站在JVM的角度來看就簡單了,因為JVM在構造異常實例時需要生成該異常的棧軌跡。這個操作會逐一訪問當前

線程的棧幀,並且記錄下各種調試信息,包括棧幀所指向方法的名字,方法所在的類名、文件名,以及在代碼中的第幾行觸發該異常等信息。雖然具體不清楚JVM的實現細節,但

是看描述這件事情也是比較費時費力的。

參考

深入拆解 Java 虛擬機(鄭雨迪)



只要自己變優秀了,其他的事情才會跟着好起來(少將7)


免責聲明!

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



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