前言
無(wú)論你是使用何種編程語(yǔ)言,在日常的開發(fā)過(guò)程中,都會(huì)不可避免的要處理異常。今天本文將嘗試講解一些jvm如何處理異常問(wèn)題,希望能夠講清楚這個(gè)內(nèi)部的機(jī)制,如果對(duì)大家有所啟發(fā)和幫助,則甚好。
當(dāng)異常不僅僅是異常
我們?cè)跇?biāo)題中提到了異常,然而這里指的異常并不是單純的exception,而是更為寬泛的throwable。只是我們工作中習(xí)以為常的將它們(錯(cuò)誤地)這樣稱謂。
關(guān)于exception和throwable的關(guān)系簡(jiǎn)單描述一下
- exception屬于throwable的子類,throwable的另一個(gè)重要的子類是error
- throw可以拋出的都是throwable和其子類,catch可捕獲的也是throwable和其子類。
除此之外,但是exception也有一些需要我們?cè)俅螐?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è)計(jì)的好壞的分析。
exception table 異常表
提到j(luò)vm處理異常的機(jī)制,就需要提及exception table,以下稱為異常表。我們暫且不急于介紹異常表,先看一個(gè)簡(jiǎn)單的java處理異常的小例子。
1
2
3
4
5
6
7
|
public static void simpletrycatch() { try { testnpe(); } catch (exception e) { e.printstacktrace(); } } |
上面的代碼是一個(gè)很簡(jiǎn)單的例子,用來(lái)捕獲處理一個(gè)潛在的空指針異常。
當(dāng)然如果只是看簡(jiǎn)簡(jiǎn)單單的代碼,我們很難看出什么高深之處,更沒(méi)有了今天文章要談?wù)摰膬?nèi)容。
所以這里我們需要借助一把神兵利器,它就是javap,一個(gè)用來(lái)拆解class文件的工具,和javac一樣由jdk提供。
然后我們使用javap來(lái)分析這段代碼(需要先使用javac編譯)
1
2
3
4
5
6
7
8
9
10
11
12
|
//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)該會(huì)有會(huì)心一笑,因?yàn)榻K于看到了exception table,也就是我們要研究的異常表。
異常表中包含了一個(gè)或多個(gè)異常處理者(exception handler)的信息,這些信息包含如下
- from 可能發(fā)生異常的起始點(diǎn)
- to 可能發(fā)生異常的結(jié)束點(diǎn)
- target 上述from和to之前發(fā)生異常后的異常處理者的位置
- type 異常處理者處理的異常的類信息
那么異常表用在什么時(shí)候呢
答案是異常發(fā)生的時(shí)候,當(dāng)一個(gè)異常發(fā)生時(shí)
1.jvm會(huì)在當(dāng)前出現(xiàn)異常的方法中,查找異常表,是否有合適的處理者來(lái)處理
2.如果當(dāng)前方法異常表不為空,并且異常符合處理者的from和to節(jié)點(diǎn),并且type也匹配,則jvm調(diào)用位于target的調(diào)用者來(lái)處理。
3.如果上一條未找到合理的處理者,則繼續(xù)查找異常表中的剩余條目
4.如果當(dāng)前方法的異常表無(wú)法處理,則向上查找(彈棧處理)剛剛調(diào)用該方法的調(diào)用處,并重復(fù)上面的操作。
5.如果所有的棧幀被彈出,仍然沒(méi)有處理,則拋給當(dāng)前的thread,thread則會(huì)終止。
6.如果當(dāng)前thread為最后一個(gè)非守護(hù)線程,且未處理異常,則會(huì)導(dǎo)致jvm終止運(yùn)行。
以上就是jvm處理異常的一些機(jī)制。
try catch -finally
除了簡(jiǎn)單的try-catch外,我們還常常和finally做結(jié)合使用。比如這樣的代碼
1
2
3
4
5
6
7
8
9
|
public static void simpletrycatchfinally() { try { testnpe(); } catch (exception e) { e.printstacktrace(); } finally { system.out.println( "finally" ); } } |
同樣我們使用javap分析一下代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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ù),而我們僅僅捕獲了一個(gè)exception
- 異常表的后兩個(gè)item的type為any
上面的三條異常表item的意思為
- 如果0到3之間,發(fā)生了exception類型的異常,調(diào)用14位置的異常處理者。
- 如果0到3之間,無(wú)論發(fā)生什么異常,都調(diào)用30位置的處理者
- 如果14到19之間(即catch部分),不論發(fā)生什么異常,都調(diào)用30位置的處理者。
再次分析上面的java代碼,finally里面的部分已經(jīng)被提取到了try部分和catch部分。我們?cè)俅握{(diào)一下代碼來(lái)看一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public static void simpletrycatchfinally(); code: //try 部分提取finally代碼,如果沒(méi)有異常發(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代碼,如果沒(méi)有異常發(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 //如果異常沒(méi)有被catch捕獲,而是到了這里,執(zhí)行完finally的語(yǔ)句后,仍然要把這個(gè)異常拋出去,傳遞給調(diào)用處。 41 : return |
catch先后順序的問(wèn)題
我們?cè)诖a中的catch的順序決定了異常處理者在異常表的位置,所以,越是具體的異常要先處理,否則就會(huì)出現(xiàn)下面的問(wèn)題
1
2
3
4
5
6
7
8
9
|
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(); } } |
這段代碼會(huì)導(dǎo)致編譯失敗,因?yàn)橄炔东@throwable后捕獲exception,會(huì)導(dǎo)致后面的catch永遠(yuǎn)無(wú)法被執(zhí)行。
return 和finally的問(wèn)題
這算是我們擴(kuò)展的一個(gè)相對(duì)比較極端的問(wèn)題,就是類似這樣的代碼,既有return,又有finally,那么finally導(dǎo)致會(huì)不會(huì)執(zhí)行
1
2
3
4
5
6
7
8
9
10
|
public static string trycatchreturn() { try { testnpe(); return "ok" ; } catch (exception e) { return "error" ; } finally { system.out.println( "trycatchreturn" ); } } |
答案是finally會(huì)執(zhí)行,那么還是使用上面的方法,我們來(lái)看一下為什么finally會(huì)執(zhí)行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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 有未處理的異常,拋出去。 |
行文倉(cāng)促,加之本人水平有限,有錯(cuò)誤的地方,請(qǐng)指出。
參考文章:
- 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é)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:https://droidyue.com/blog/2018/10/21/how-jvm-handle-exceptions/