一、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看異常到此。
(引:極客時間)