《深入理解Java虛擬機器》- JVM如何進行異常處理
一、Java異常
在程式中,錯誤可能產生於程式設計師沒有預料到的各種情況,或者超出程式設計師可控範圍的環境,例如使用者的壞資料、試圖開啟一個不存在的檔案等。為了能夠及時有效地處理程式中的執行錯誤,Java 專門引入了異常類。
二、Java常見異常分類
三、為什麼產生異常
在 Java 中一個異常的產生,主要有如下三種原因:
- Java 內部錯誤發生異常,Java 虛擬機器產生的異常。
- 編寫的程式程式碼中的錯誤所產生的異常,例如空指標異常、陣列越界異常等。這種異常稱為未檢査的異常,一般需要在某些類中集中處理這些異常。
- 通過 throw 語句手動生成的異常,這種異常稱為檢査的異常,一般用來告知該方法的呼叫者一些必要的資訊。
四、碰到異常怎麼辦?
我們把生成異常物件,並把它提交給執行時系統的過程稱為丟擲(throw)異常。執行時系統在方法的呼叫棧中查詢,直到找到能夠處理該型別異常的物件,這一個過程稱為捕獲(catch)異常。
Java 異常強制使用者考慮程式的強健性和安全性。異常處理不應用來控制程式的正常流程,其主要作用是捕獲程式在執行時發生的異常並進行相應處理。編寫程式碼處理某個方法可能出現的異常,可遵循如下三個原則:
- 在當前方法宣告中使用 try catch 語句捕獲異常。
- 一個方法被覆蓋時,覆蓋它的方法必須丟擲相同的異常或異常的子類。
- 如果父類丟擲多個異常,則覆蓋方法必須丟擲那些異常的一個子集,而不能拋出新異常。
(引用:http://c.biancheng.net/view/1038.html)
五、從JVM角度看異常的產生與表達
先看示例程式碼:
public class Foo { private int tryBlock; private int catchBlock; private int finallyBlock; private int methodExit; public void test() { try { tryBlock = 0; } catch (Exception e) { catchBlock = 1; } finally { finallyBlock = 2; } methodExit = 3; } }
這段程式碼是一段簡單的異常處理程式碼,我們可以通過javap檢視class檔案的表達形式:
public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: iconst_0 2: putfield #2 // Field tryBlock:I 5: aload_0 6: iconst_2 7: putfield #3 // Field finallyBlock:I 10: goto 35 13: astore_1 14: aload_0 15: iconst_1 16: putfield #5 // Field catchBlock:I 19: aload_0 20: iconst_2 21: putfield #3 // Field finallyBlock:I 24: goto 35 27: astore_2 28: aload_0 29: iconst_2 30: putfield #3 // Field finallyBlock:I 33: aload_2 34: athrow 35: aload_0 36: iconst_3 37: putfield #6 // Field methodExit:I 40: return Exception table: from to target type 0 5 13 Class java/lang/Exception 0 5 27 any 13 19 27 any LineNumberTable: line 10: 0 line 14: 5 line 15: 10 line 11: 13 line 12: 14 line 14: 19 line 15: 24 line 14: 27 line 16: 35 line 17: 40 StackMapTable: number_of_entries = 3 frame_type = 77 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] frame_type = 77 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ] frame_type = 7 /* same */
從位元組碼中的註釋可以看到,finally塊被新增到了三個地方。也就是說,在從java程式碼翻譯成位元組碼檔案時,jvm會為try塊和catch塊生成finally 塊裡的邏輯。但是想想,為什麼是三個“finally”呢? 最後一個finally 是為在catch塊中的程式碼執行時發生異常而準備的。那麼,有人會問,finally塊的程式碼如果還有報錯怎麼辦呢? 這裡,引進沒有被本人證實的事實:會往外丟擲去,給上一層程式碼進行處理。
這裡說明一下黃色部分的位元組碼:
exception table 表示異常表,異常表是用於儲存程式碼中涉及到的所有異常,每個類編譯後,都會跟隨一個異常表,如果發生異常,首先在異常表中查詢對應的行(即程式碼中相應的
try{}catch(){}
程式碼塊),如果找到,則跳轉到異常處理程式碼執行,如果沒有找到,則返回(執行 finally 之後),並 copy 異常的應用給父呼叫者,接著查詢父呼叫的異常表,以此類推。from...to:表示異常處理器監控的範圍(比如try塊包含的程式碼)
target:表示異常處理器起始的位置(比如catch塊包含的程式碼)
type:就是處理的異常
那麼,發生異常後,如何對照異常表?
當程式觸發異常後,Java虛擬機器會從上到下遍歷異常表中的條目。當觸發異常的位元組碼的索引值在某個異常表條目的監控範圍內,Java虛擬機器會判斷所丟擲的異常和該條目想要捕獲的異常是否匹配。如果匹配,Java虛擬機器會將控制流轉移到該條目的target指標指向的程式碼上,繼續程式執行。
下面,提及的位元組碼解析一下異常表:
程式開始,執行到1:iconst_0時,發生Exception異常,此時程式會去便利方法表,從第一行開始,檢測到 0<1<5,符合第一條目檢測範圍,接著再檢視丟擲的異常為Exception,符合該條目捕獲處理的異常,後跳轉至序號13位元組碼繼續執行。若再在14:aload_0發生異常時,程式就又跳到異常表,查詢匹配異常條目,最終找到target為序號為27的位元組碼,然後便一直往下走完所有位元組碼。
上例子中,屬於在catch塊發生異常,所以會看到位元組碼後還有一個athrow的步驟,也就是往外丟擲異常啦。
好了,Jvm看異常到此。
(引:極客時