try catch异常后会执行后面的代码吗_JVM异常处理最强讲解
各種編程語言都有自己的異常捕獲、處理方式。在程序指令執行過程中,可能會發生可預知或不可預知的各種異常。通過異常捕獲和處理可以記錄相關異常日志,執行一些補救策略,讓局部的錯誤不影響服務整體可用性。
那么Java的異常處理代碼是如何被編譯的?JVM又是如何捕獲異常、執行異常處理邏輯的?
異常的拋出
在Java編程語言中,使用throw關鍵字來拋出異常
編譯后:
0 iload_1 1 ifne 12 4 new #2 <com/supercoder/jvm/TestExc> 7 dup 8 invokespecial #3 <com/supercoder/jvm/TestExc.<init>> 11 athrow 12 return
athrow指令從操作數棧棧頂拿到一個TestExc實例的引用拋了出去。TestExc實例創建的過程不在這里講了,在實例創建和方法調用的相關章節有詳細講解,可以找一找歷史文章復習一下。
接著往下看,如何捕獲、處理異常?
異常捕獲——try-catch
在Java編程語言中,使用try將代碼括起來,使用catch捕獲指定的異常類型。try-catch支持有一個或多個catch子句,并且支持嵌套。
單個catch子句
void catchOne() {try {tryItOut();} catch (TestExc e) {handleExc(e);} }編譯后:
Method void catchOne() 0 aload_0 1 invokevirtual #6 4 return 5 astore_1 6 aload_0 7 aload_1 8 invokevirtual #5 11 return Exception table: From To Target Type 0 4 5 Class TestExc使用了try-catch之后,try代碼塊在編譯時并沒有特殊的改變,和沒有try時一樣。如果try代碼塊在執行過程中沒有拋出任何異常,那么會和沒有try時的表現一樣:完成對tryItOut方法的調用,且catchOne方法正常返回。對handleExc方法的調用、以及catch子句中的內容都會作為正常的方法調用被編譯。緊跟在try代碼塊指令后面的就是單個catch子句的指令。
由于catch子句的存在,編譯器會生成一個異常表,[From,To)是一個左閉右開的區間,Target指向對應的異常處理器第一個指令位置。catchOne方法的異常表中有一個關聯到TestExc實例的條目,catchOne方法的catch子句可以訪問這個TestExc實例。在執行索引是0到4區間的指令時,如果有TestExc類型的值被拋出,控制將會轉移到索引為5的指令,即catch子句代碼塊。如果拋出的值不是TestExc類型,catch子句是不能被執行的,這個值會被重新拋給catchOne方法的調用者。
多個catch子句
一個try也可以有多個catch子句。
void catchTwo() {try {tryItOut();} catch (TestExc1 e) {handleExc(e);} catch (TestExc2 e) {handleExc(e);} }編譯后:
Method void catchTwo() 0 aload_0 1 invokevirtual #5 4 return 5 astore_1 6 aload_0 7 aload_1 8 invokevirtual #7 11 return 12 astore_1 13 aload_0 14 aload_1 15 invokevirtual #7 18 return Exception table: From To Target Type 0 4 5 Class TestExc1 0 4 12 Class TestExc2一個try聲明的多個catch子句在編譯時也很簡單,每一個catch子句編譯后的指令序列都跟在另一個catch子句對應的指令后面,并且會添加一個條目到異常表中。
在索引0到4區間的try代碼塊運行期間,如果有一個值被拋出,該值的類型和一個或多個catch子句參數中的實例相匹配時,匹配的第一個catch子句會被選中,控制轉移到這個catch子句的代碼塊。反之,如果拋出的值不能跟任何一個catch子句參數中的實例匹配。JVM會重新把這個值拋出給catchTwo方法的調用者,并且不會執行任何一個catch子句。
嵌套try-catch
嵌套try-catch的編譯跟多個catch子句的編譯非常相似。
void nestedCatch() {try {try {tryItOut();} catch (TestExc1 e) {handleExc1(e);}} catch (TestExc2 e) {handleExc2(e);} }編譯后:
Method void nestedCatch() 0 aload_0 1 invokevirtual #8 4 return 5 astore_1 6 aload_0 7 aload_1 8 invokevirtual #7 11 return 12 astore_1 13 aload_0 14 aload_1 15 invokevirtual #6 Exception table: From To Target Type 0 4 5 Class TestExc1 0 12 12 Class TestExc2try-catch的嵌套只會在異常表中有所體現。每一個try-catch都對應異常表中的一個條目,它們From和To所表示的區間會出現交集。JVM并沒有強制異常表條目的嵌套以及排序規則,不過,由于try-catch的構造是結構化的,編譯器是可以對異常處理表中這些結構化數據進行排序的(例如可以按照Target從低到高排序)。排序之后,對于方法中拋出的任何異常,第一個與之匹配的異常處理程序就是最內層的匹配catch子句,內層不能匹配時會繼續向外層嘗試匹配。
異常處理中的兜底——finally
無論try代碼塊中是否有異常拋出,無論異常是否被catch住,finally代碼塊一定會被執行。
沒有catch的try-finally
生成class文件的版本為50.0(即JDK1.6)及以下的編譯器,在編譯包含finally的異常處理代碼時可能會同時使用兩個特殊的指令:jsr(跳轉到子程序)、ret(從子程序返回)。finally子句被作為方法的子程序被編譯,類似異常處理器。
void tryFinally() {try {tryItOut();} finally {wrapItUp();} }編譯后:
Method void tryFinally() 0 aload_0 1 invokevirtual #6 4 jsr 14 7 return 8 astore_1 9 jsr 14 12 aload_1 13 athrow 14 astore_2 15 aload_0 16 invokevirtual #5 19 ret 2 Exception table: From To Target Type 0 4 8 any當沒有catch,只有finally時,編譯器也會在異常表中生成一個條目,Type是any,任何類型的異常都能夠與之相匹配。當調用子程序的jsr指令被執行時,緊跟在jsr后指令的地址會作為returnAddress類型先被壓入操作數棧,子程序代碼會將這個地址存入局部變量表。子程序代碼的尾部會有一個ret指令,該指令從局部變量表獲取到返回地址并將控制轉移到該地址處的指令。
完整的異常處理的結構——try-catch-finally
void tryCatchFinally() {try {tryItOut();} catch (TestExc e) {handleExc(e);} finally {wrapItUp();} }編譯后:
Method void tryCatchFinally() 0 aload_0 //將局部變量表0處的this壓入操作數棧(當前方法的調用者引用) 1 invokevirtual #4 //調用tryItOut方法 4 goto 16 //控制轉移到index 16,執行jsr指令 7 astore_3 //[0,4)之間出現了異常,拋出的異常值存入局部變量表3的位置 8 aload_0 //this壓棧 9 aload_3 //異常值壓棧 10 invokevirtual #6 //調用handleExc方法 13 goto 16 //控制轉移到index 16,執行jsr指令 16 jsr 26 //控制轉移到index 26,執行finally子程序(會先把19壓入操作數棧) 19 return //執行finally子程序后,本次方法調用返回 20 astore_1 //TestExc類型不能與異常值類型匹配,異常值存入局部變量表1的位置 21 jsr 26 //控制轉移到index 26,執行finally子程序(會先把24壓入操作數棧) 24 aload_1 //將局部變量表1處的異常值壓入操作數棧 25 athrow //將異常重新拋出 26 astore_2 //從操作數棧彈出原始執行位置(19或24),存入局部變量表2的位置 27 aload_0 //局部變量表0處的this壓入操作數棧 28 invokevirtual #5 //調用wrapItUp() 31 ret 2 //從局部變量表2的位置取出原始指令索引(19或24),控制轉移到對應位置 Exception table: From To Target Type 0 4 7 Class TestExc 0 16 20 any當try代碼塊正常完成時,會由goto指令將控制轉移到索引16的位置,調用jsr指令去執行finally子程序。如果try代碼塊發生了異常且異常類型是TestExc,根據異常表的Target,控制會轉移到索引7的位置,執行對應的異常處理邏輯,之后還會由goto指令將控制轉移到索引16的位置,同樣由jsr去調用finally子程序。若異常類型不能與catch子句的異常類型匹配,則會與any匹配,對應的異常處理邏輯會先調用finally子程序,然后將異常繼續拋出給方法的調用者。
finally何時被執行?
有4種不同的方式,可以讓finally子程序被調用。
50.0版本之后finally是如何被編譯的?
50.0版本之后(從JDK1.7開始),不再使用jsr和ret指令執行finally指令的調用。取而代之的是一種非常簡單暴力的方式:將finally子程序的指令序列編譯到在每一個需要調用finally子程序的位置,這樣做的代價就是字節碼文件有所膨脹。
我們以JDK1.8為例,有如下代碼:
public int test() {int i = 1;try {i++;return i;} catch (Exception e) {i++;return i;} finally {i = 10;} }編譯后:
0 iconst_1 1 istore_1 2 iinc 1 by 1 5 iload_1 6 istore_2 7 bipush 10 //try正常執行,方法return之前執行finally邏輯 9 istore_1 10 iload_2 11 ireturn 12 astore_2 //異常與catch子句匹配,執行異常處理 13 iinc 1 by 1 16 iload_1 17 istore_3 18 bipush 10 //catch子句邏輯執行完,在return之前先執行finally邏輯 20 istore_1 21 iload_3 22 ireturn 23 astore 4 25 bipush 10 //try代碼塊拋出了異常,catch子句沒有匹配上,先執行finally邏輯,然后將異常重新拋出給調用者 27 istore_1 28 aload 4 30 athrow Exception table: From To Target Type 2 7 12 java/lang/Exception 2 7 23 any 12 18 23 any 23 25 23 any從編譯結果可以看到,沒有出現jsr和ret指令,finally的指令序列重復出現在三個地方。
經典面試題
在本文最后一個例子中,test方法的局部變量:i,其值最終是多少?test方法的返回值又是多少?結合編譯結果進行分析,歡迎留言討論。
總結
以上是生活随笔為你收集整理的try catch异常后会执行后面的代码吗_JVM异常处理最强讲解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python的目的及应用_python
- 下一篇: 大数据和python哪个好_大数据语言之