JVM如何處理異常深入詳解
前言
無論你是使用何種編程語言,在日常的開發(fā)過程中,都會不可避免的要處理異常。今天本文將嘗試講解一些JVM如何處理異常問題,希望能夠講清楚這個內(nèi)部的機(jī)制,如果對大家有所啟發(fā)和幫助,則甚好。
當(dāng)異常不僅僅是異常
我們在標(biāo)題中提到了異常,然而這里指的異常并不是單純的Exception,而是更為寬泛的Throwable。只是我們工作中習(xí)以為常的將它們(錯誤地)這樣稱謂。
關(guān)于Exception和Throwable的關(guān)系簡單描述一下
- Exception屬于Throwable的子類,Throwable的另一個重要的子類是Error
- throw可以拋出的都是Throwable和其子類,catch可捕獲的也是Throwable和其子類。
除此之外,但是Exception也有一些需要我們再次強(qiáng)調(diào)的
- Exception分為兩種類型,一種為Checked Exception,另一種為unchecked Exception
- Checked Exception,比如最常見的IOException,這種異常需要調(diào)用處顯式處理,要么使用try catch捕獲,要么再次拋出去。
- Unchecked Exception指的是所有繼承自Error(包含自身)或者是RuntimeException(包含自身)的類。這些異常不強(qiáng)制在調(diào)用處進(jìn)行處理。但是也可以try catch處理。
注:本文暫不做Checked Exception設(shè)計的好壞的分析。
Exception Table 異常表
提到JVM處理異常的機(jī)制,就需要提及Exception Table,以下稱為異常表。我們暫且不急于介紹異常表,先看一個簡單的Java處理異常的小例子。
public static void simpleTryCatch() {
try {
testNPE();
} catch (Exception e) {
e.printStackTrace();
}
}
上面的代碼是一個很簡單的例子,用來捕獲處理一個潛在的空指針異常。
當(dāng)然如果只是看簡簡單單的代碼,我們很難看出什么高深之處,更沒有了今天文章要談?wù)摰膬?nèi)容。
所以這里我們需要借助一把神兵利器,它就是javap,一個用來拆解class文件的工具,和javac一樣由JDK提供。
然后我們使用javap來分析這段代碼(需要先使用javac編譯)
//javap -c Main public static void simpleTryCatch(); Code: 0: invokestatic #3 // Method testNPE:()V 3: goto 11 6: astore_0 7: aload_0 8: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V 11: return Exception table: from to target type 0 3 6 Class java/lang/Exception
看到上面的代碼,應(yīng)該會有會心一笑,因為終于看到了Exception table,也就是我們要研究的異常表。
異常表中包含了一個或多個異常處理者(Exception Handler)的信息,這些信息包含如下
- from 可能發(fā)生異常的起始點(diǎn)
- to 可能發(fā)生異常的結(jié)束點(diǎn)
- target 上述from和to之前發(fā)生異常后的異常處理者的位置
- type 異常處理者處理的異常的類信息
那么異常表用在什么時候呢
答案是異常發(fā)生的時候,當(dāng)一個異常發(fā)生時
1.JVM會在當(dāng)前出現(xiàn)異常的方法中,查找異常表,是否有合適的處理者來處理
2.如果當(dāng)前方法異常表不為空,并且異常符合處理者的from和to節(jié)點(diǎn),并且type也匹配,則JVM調(diào)用位于target的調(diào)用者來處理。
3.如果上一條未找到合理的處理者,則繼續(xù)查找異常表中的剩余條目
4.如果當(dāng)前方法的異常表無法處理,則向上查找(彈棧處理)剛剛調(diào)用該方法的調(diào)用處,并重復(fù)上面的操作。
5.如果所有的棧幀被彈出,仍然沒有處理,則拋給當(dāng)前的Thread,Thread則會終止。
6.如果當(dāng)前Thread為最后一個非守護(hù)線程,且未處理異常,則會導(dǎo)致JVM終止運(yùn)行。
以上就是JVM處理異常的一些機(jī)制。
try catch -finally
除了簡單的try-catch外,我們還常常和finally做結(jié)合使用。比如這樣的代碼
public static void simpleTryCatchFinally() {
try {
testNPE();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Finally");
}
}
同樣我們使用javap分析一下代碼
public static void simpleTryCatchFinally(); Code: 0: invokestatic #3 // Method testNPE:()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
和之前有所不同,這次
- 異常表中,有三條數(shù)據(jù),而我們僅僅捕獲了一個Exception
- 異常表的后兩個item的type為any
上面的三條異常表item的意思為
- 如果0到3之間,發(fā)生了Exception類型的異常,調(diào)用14位置的異常處理者。
- 如果0到3之間,無論發(fā)生什么異常,都調(diào)用30位置的處理者
- 如果14到19之間(即catch部分),不論發(fā)生什么異常,都調(diào)用30位置的處理者。
再次分析上面的Java代碼,finally里面的部分已經(jīng)被提取到了try部分和catch部分。我們再次調(diào)一下代碼來看一下
public static void simpleTryCatchFinally(); Code: //try 部分提取finally代碼,如果沒有異常發(fā)生,則執(zhí)行輸出finally操作,直至goto到41位置,執(zhí)行返回操作。 0: invokestatic #3 // Method testNPE:()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 //catch部分提取finally代碼,如果沒有異常發(fā)生,則執(zhí)行輸出finally操作,直至執(zhí)行g(shù)ot到41位置,執(zhí)行返回操作。 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 //finally部分的代碼如果被調(diào)用,有可能是try部分,也有可能是catch部分發(fā)生異常。 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 //如果異常沒有被catch捕獲,而是到了這里,執(zhí)行完finally的語句后,仍然要把這個異常拋出去,傳遞給調(diào)用處。 41: return
Catch先后順序的問題
我們在代碼中的catch的順序決定了異常處理者在異常表的位置,所以,越是具體的異常要先處理,否則就會出現(xiàn)下面的問題
private static void misuseCatchException() {
try {
testNPE();
} catch (Throwable t) {
t.printStackTrace();
} catch (Exception e) { //error occurs during compilings with tips Exception Java.lang.Exception has already benn caught.
e.printStackTrace();
}
}
這段代碼會導(dǎo)致編譯失敗,因為先捕獲Throwable后捕獲Exception,會導(dǎo)致后面的catch永遠(yuǎn)無法被執(zhí)行。
Return 和finally的問題
這算是我們擴(kuò)展的一個相對比較極端的問題,就是類似這樣的代碼,既有return,又有finally,那么finally導(dǎo)致會不會執(zhí)行
public static String tryCatchReturn() {
try {
testNPE();
return "OK";
} catch (Exception e) {
return "ERROR";
} finally {
System.out.println("tryCatchReturn");
}
}
答案是finally會執(zhí)行,那么還是使用上面的方法,我們來看一下為什么finally會執(zhí)行。
public static java.lang.String tryCatchReturn(); Code: 0: 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 16: astore_0 17: ldc #10 // String ERROR 19: astore_1 20: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 23: ldc #8 // String tryCatchReturn 25: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 28: aload_1 29: areturn //返回ERROR字符串 30: astore_2 31: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 34: ldc #8 // String tryCatchReturn 36: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 39: aload_2 40: athrow 如果catch有未處理的異常,拋出去。
行文倉促,加之本人水平有限,有錯誤的地方,請指出。
參考文章:
- http://blog.jamesdbloom.com/JVMInternals.html#exception_table
- https://blog.takipi.com/the-surprising-truth-of-java-exceptions-what-is-really-going-on-under-the-hood/
- https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
- https://dzone.com/articles/the-truth-of-java-exceptions-whats-really-going-on
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Java中CountDownLatch進(jìn)行多線程同步詳解及實例代碼
這篇文章主要介紹了Java中CountDownLatch進(jìn)行多線程同步詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03
java微信開發(fā)API第三步 微信獲取以及保存接口調(diào)用憑證
這篇文章主要為大家詳細(xì)介紹了java微信開發(fā)API第二步,微信獲取以及保存接口調(diào)用憑證,感興趣的小伙伴們可以參考一下2016-06-06

