關于異常

參考資料:JVM如何處理異常深入詳解

一、Java 異常的概念和分類

所有的異常都派生于Throwable類的一個實例


無標題.jpg

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()并分發

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容