JVM如何捕獲異常?

Java異常知識

1.異常的兩大關鍵因素

(1)拋出異常
1.顯式:應用程序手動拋出異常。具體就是使用throw拋出異常
2.隱式:Java虛擬機對於無法執行的代碼,自動拋出異常
(2)捕獲異常
1.try 代碼塊:用來標記需要進行異常監控的代碼。
2.catch 代碼塊:跟在 try 代碼塊之後,用來捕獲在 try 代碼塊中觸發的某種指定類型的異常。除了聲明所捕獲異常的類型之外,catch 代碼塊還定義了針對該異常類型的異常處理器。在 Java中,try 代碼塊後面可以跟着多個 catch 代碼塊,來捕獲不同類型的異常。Java 虛擬機會從上至下匹配異常處理器。因此,前面的 catch 代碼塊所捕獲的異常類型不能覆蓋後邊的,否則編譯器會報錯。
3.fnally 代碼塊:跟在 try 代碼塊和 catch 代碼塊之後,用來聲明一段必定運行的代碼。它的設計初衷是爲了避免跳過某些關鍵的清理代碼,例如關閉已打開的系統資源。在程序正常執行的情況下,這段代碼會在 try 代碼塊之後運行。否則,也就是 try 代碼塊觸發異常的情況下,如果該異常沒有被捕獲,fnally 代碼塊會直接運行,並且在運行之後重新拋出該異常。如果該異常被 catch 代碼塊捕獲,fnally 代碼塊則在 catch 代碼塊之後運行。在某些不幸的情況下,catch 代碼塊也觸發了異常,那麼 fnally 代碼塊同樣會運行,並會拋出 catch 代碼塊觸發的異常。在某些極端不幸的情況下,fnally 代碼塊也觸發了異常,那麼只好中斷當前 fnally 代碼塊的執行,並往外拋異常。

2.異常的分類

圖片描述

1.所有異常的父類都是Throwable
2.Error異常是程序的執行狀態無法恢復的狀態,只能中止線程甚至中止JVM的異常
3.Exception是相對Error沒有這麼嚴重的異常
4.Runtime Exception和Error都屬於不需要檢查的異常
5.除了Runtime Exception和Error的異常都是Check Exception異常
6.Check Exception異常都是需要顯式捕獲的異常

3.Java虛擬機是如何捕獲異常的?

java虛擬機構造異常實例非常昂貴。虛擬機需要生成該異常的棧軌跡。該操作會逐一訪問當前線程的 Java 棧幀,並且記錄下各種調試信息,包括棧幀所指向方法的名字,方法所在的類名、文件名,以及在代碼中的第幾行觸發該異常。
既然異常實例的構造十分昂貴,我們是否可以緩存異常實例,在需要用到的時候直接拋出呢?從語法角度上來看,這是允許的。然而,該異常對應的棧軌跡並非 throw 語句的位置,而是新建異常的位置。
因此,這種做法可能會誤導開發人員,使其定位到錯誤的位置。這也是爲什麼在實踐中,我們往往選擇拋出新建異常實例的原因。

異常處理器
1.來源:每個方法在編譯的時候都會生成一個異常表。異常表裏面的每一個條目都代表一個異常處理器。
2.組成:
(1)from指針,to指針:代表捕獲異常的範圍,就是Try的範圍。
(2)target指針:代表處理器的開始位置,就是catch的起始位置。
(3)捕獲的異常類型。
3.捕獲異常
(1)當程序觸發異常時,Java 虛擬機會從上至下遍歷異常表中的所有條目。當觸發異常的字節碼的索引值在某個異常表條目的監控範圍內,Java 虛擬機會判斷所拋出的異常和該條目想要捕獲的異常是否匹配。如果匹配,Java 虛擬機會將控制流轉移至該條目 target 指針指向的字節碼。
(2)如果遍歷完所有異常表條目,Java 虛擬機仍未匹配到異常處理器,那麼它會彈出當前方法對應的Java 棧幀,並且在調用者(caller)中重複上述操作。在最壞情況下,Java 虛擬機需要遍歷當前線程 Java 棧上所有方法的異常表。
4.finally代碼的編譯:當前版本 Java 編譯器的做法,是複製 fnally 代碼塊的內容,分別放在 try-catch 代碼塊所有正常執行路徑以及異常執行路徑的出口中。

代碼1:
Try{
Try block
} catch {
Catch block
} finally {
Finally block
}
代碼2:
Try {
Try block
Finally block
} catch {
Catch block
Finally block
} finally{
Finally block
}
代碼1是我們的Java代碼,代碼2是編譯之後的Java代碼。

注意:如果 catch 代碼塊捕獲了異常,並且觸發了另一個異常,那麼 fnally 捕獲並且重拋的異常是哪個呢?答案是後者。也就是說原本的異常便會被忽略掉,這對於代碼調試來說十分不利。

5.Java7的 Supressed 異常以及語法糖

針對上節說的會將catch的異常忽略掉,Java7引入了 Supressed 異常處理這個問題。但是使用起來還是很麻煩(沒有感受,😔)。
爲此,Java 7 專門構造了一個名爲 try-with-resources 的語法糖,在字節碼層面自動使用Supressed 異常。當然,該語法糖的主要目的並不是使用 Supressed 異常,而是精簡資源打開關閉的用法。
資源的關閉操作本身容易觸發異常。所以爲了讓每一個資源都關閉,所以每一個資源都要寫個try catch 這樣太過繁瑣,所以try-with-resources就很好的解決了這個問題。程序可以在 try 關鍵字後聲明並實例化實現了 AutoCloseable 接口的類,編譯器將自動添加對應的 close() 操作。在聲明多個AutoCloseable 實例的情況下,編譯生成的字節碼類似於上面手工編寫代碼的編譯結果。與手工代碼相比,try-with-resources 還會使用 Supressed 異常的功能,來避免原異常“被消失”。
除了 try-with-resources 語法糖之外,Java 7 還支持在同一 catch 代碼塊中捕獲多種異常。實際實現非常簡單,生成多個異常表條目即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章