參考資料:JVM如何處理異常深入詳解
一、Java 異常的概念和分類
所有的異常都派生于Throwable類的一個實例
Error
java 運行時的系統錯誤和資源耗盡錯誤。 應用程序是不應該跑出這種類型的對象。一般出現錯誤,也沒什么辦法
Exception
RunTimeException和其他Exception(主要是IO Exception)
RunTimeException:為程序的邏輯錯誤
如:類型錯誤,數組越界,空指針
其他Exception(主要是IO Exception):程序本身沒問題,但由于像I/O這樣的錯誤導致異常
如:打開一個不存在的文件,根據字符串找class炸不到等
unchecked 和checked
java將error和RunTimeException歸為unchecked 異常,其他為checked 異常
原則上checked 異常要進行聲明,而unchecked 異常要么控制不了,如error,要么應該避免發生RunTimeException,不需要捕獲
二、JVM如何處理異常
異常表 Exception Table
異常表的構成
from 可能發生異常的起始點
to 可能發生異常的結束點
target 上述from和to之前發生異常后的異常處理者的位置
type 異常處理者處理的異常的類信息異常表的調用
1.JVM會在當前出現異常的方法中,查找異常表,是否有合適的處理者來處理
2.如果當前方法異常表不為空,并且異常符合處理者的from和to節點,并且type也匹配,則JVM調用位于target的調用者來處理。
3.如果上一條未找到合理的處理者,則繼續查找異常表中的剩余條目
4.如果當前方法的異常表無法處理,則向上查找(彈棧處理)剛剛調用該方法的調用處,并重復上面的操作。
5.如果所有的棧幀被彈出,仍然沒有處理,則拋給當前的Thread,Thread則會終止。
6.如果當前Thread為最后一個非守護線程,且未處理異常,則會導致JVM終止運行。catch的順序
catch的順序決定了異常處理者在異常表的位置
因為我們實際中可以catch很多的exception 類型,所以越具體的exception要放在前面,寬泛的exception或者throwable一般都用于兜底finally的執行
finally是如何真正執行的,當前編譯器的做法是復制finnal 代碼塊的內容,分別放在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
這三份finally代碼塊都放在什么位置:
第一份位于try代碼后 : 若果try中代碼正常執行,沒有異常那么finally代碼就在這里執行。
第二份位于catch代碼后 : 如果try中有異常同時被catch捕獲,那么finally代碼就在這里執行。
第三份位于異常執行路徑 : 如果如果try中有異常但沒有被catch捕獲,或者catch又拋異常,那么就執行最終的finally代碼
- return 和 finally
這個其實不必要強記,只需要你知道java編譯器的原理就可以了,java編譯器將finally編譯進去后,如果有return是先執行finnal在執行return的
invokestatic #3 // Method testNPE:()V
3: ldc #6 // String OK
5: astore_0
6: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #8 // String tryCatchReturn
11: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: aload_0
15: areturn 返回OK字符串,areturn意思為return a reference from a method
三、未捕獲異常是如何處理的
當一個線程遇到的異常,并沒有被正確的捕獲,就成為了未捕獲異常,如沒有catch或catch沒有兜底的exception和throwable
-
如何設置未捕獲異常
image.png 線程發生uncaughtException,JVM怎么處理
1.每個線程有一個變量uncaughtExceptionHandler來保存未捕獲異常的handle
2.線程分發目標的handle,優先分發給當前線程的uncaughtExceptionHandler
3.上述為null時,分發給自己所在的ThreadGroup來作為未捕獲異常處理者,ThreadGroup implements Thread.UncaughtExceptionHandler
4.ThreadGroup會嘗試轉給它的父ThreadGroup(如果存在的話)
5.如果上面沒有找到對應的ThreadGroup,則嘗試獲取Thread.getDefaultUncaughtExceptionHandler()并分發