一、Java異常處理機制
Java異常的定義、類型和用法在網上有很多優秀的文章,這裏直接引用一篇寫得比較詳細的文章。
深入理解Java異常處理機制
一個簡單的異常繼承樹
接下來做一個知識點的提出,如果有哪一個點不懂,可以查看上面引用的文章進行解惑。
-
Java異常的繼承樹
- 頂層父類:Throwable
- 兩個重要子類:
Error(錯誤)
和Exception(異常)
-
異常
可查異常
和不可查異常
定義運行時異常
和非運行時異常
定義
-
try - catch - finally
- 處理順序
-
處理異常機制
- 拋出異常
- 捕獲異常
- 異常鏈
拋出異常時,異常覆蓋問題
二、異常在字節碼層面的實現
2.1 一個簡單的例子
2.1.1 例子的源碼
public class Exception1{
public static void main(String[] args){
int a,b,c,d;
try{
a =1 ;
}catch(Exception e){
b = 2;
}finally{
c = 3 ;
}
d = 4;
}
}
2.1.2 查看字節碼並分析方法執行過程
這裏只放出main函數的的部分
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=7, args_size=1
0: iconst_1 // 將int型常量1入棧
1: istore_1 // 將棧頂int型的數賦予第二個局部變量(即將常量1賦值到局部變量a)
2: iconst_3 // 將int型常量3入棧
3: istore_3 // 將棧頂的數賦予第四個局部變量(即將常量3賦值到局部變量c)
4: goto 23 // 跳轉到偏移量爲21的指令
7: astore 5 // 棧頂ref對象存入第1局部變量
9: iconst_2 // 將int型常量2入棧
10: istore_2 // 將棧頂的數賦予第三個局部變量(即將常量3賦值到局部變量b)
11: iconst_3 // 將int型常量3入棧
12: istore_3 // 將棧頂的數賦予第四個局部變量(即將常量3賦值到局部變量c)
13: goto 23
16: astore 6
18: iconst_3
19: istore_3
20: aload 6
22: athrow
23: iconst_4
24: istore 4
26: return
Exception table:
from to target type
0 2 7 Class java/lang/Exception
0 2 16 any
7 11 16 any
16 18 16 any
上面方法的執行,將a變量賦值之後會將c變量也進行賦值,將b變量賦值也會對c變量進行賦值,從這裏我們可以知道finally實現的原理: 編譯器會將finally的代碼塊全部複製一遍貼到正常代碼的下面,這樣就保證了finally的代碼塊總會被執行
。
2.1.3 異常處理過程
在上述字節碼中,有一個表是異常處理時用到的表:Exception Table
,它裏面包含了異常處理開始的偏移量、結束偏移量、異常捕捉的類型等等。下面進行詳細的介紹:
Exception table:
異常處理信息表from
異常處理開始的位置to
異常處理結束的位置from
和to
結合起來就是異常處理的位置。在上述例子中,Exception table的第一行from
和to
分別表示爲0,2
,這裏代表着異常是從0開始,到2結束(不包括2
),表示的是try包含的代碼塊。target
異常處理器的起始位置,即catch
開始處理的位置type
異常類型,any
表示所有類型
異常處理的過程:
當程序觸發異常時,Java虛擬機會遍歷異常表中的所有條目(即try裏面的所有代碼)。如果異常找到異常發生的字節碼條目,則會跟
catch
要捕捉的異常匹配,如果匹配,則開始執行catch
裏面的代碼。
如果沒有匹配到,那麼它會彈出當前方法對應的 Java 棧幀,並且在調用者(caller)中重複上述操作。在最壞情況下,Java 虛擬機需要遍歷當前線程 Java 棧上所有方法的異常表
如果在catch
中發生了異常,那麼Java虛擬機會拋棄第一個異常,嘗試捕獲並處理新的異常。這個在編碼中是很不利於調試的。
總結
- Java異常處理機制的瞭解
- finally總會被執行的原理
- 在JVM層面的異常處理過程