JVM中的異常處理
歡迎來到“Under The Hood”第六期。本期我們介紹JVM處理異常的方式,包括如何拋出和捕獲異常及相關(guān)的字節(jié)碼指令。但本文不會(huì)討論finally子句,這是下期的主題。你可能需要閱讀往期的文章才能更好的理解本文。
異常處理
在程序運(yùn)行時(shí),異常讓你可以平滑的處理意外狀況。為了演示JVM處理異常的方式,考慮NitPickyMath類,它提供對(duì)整數(shù)進(jìn)行加,減,乘,除以及取余的操作。
NitPickyMath提供的這些操作和Java語言的“+”,“-”,“*”,“/”和“%”是一樣的,除了NitPickyMath中的方法在以下情況下會(huì)拋出檢查型(checked)異常:上溢出,下溢出以及被0除。0做除數(shù)時(shí),JVM會(huì)拋出ArithmeticException異常,但是上溢出和下溢出不會(huì)引發(fā)任何異常。NitPickyMath中拋出異常的方法定義如下:
-
class OverflowException extends Exception {
-
}
-
class UnderflowException extends Exception {
-
}
-
class DivideByZeroException extends Exception {
-
}
NitPickyMath類中的remainder()方法就是一個(gè)拋出和捕獲異常的簡單方法。
-
static int remainder(int dividend, int divisor)
-
throws DivideByZeroException {
-
try {
-
return dividend % divisor;
-
}
-
catch (ArithmeticException e) {
-
throw new DivideByZeroException();
-
}
-
}
remainder()方法,只是簡單的對(duì)當(dāng)作參數(shù)傳遞進(jìn)來的2個(gè)整數(shù)進(jìn)行取余操作。如果取余操作的除數(shù)是0,會(huì)引發(fā)ArithmeticException異常。remainder()方法捕獲這個(gè)異常,并重新拋出DivideByZeroException異常。
DivideByZeroException和ArithmeticException的區(qū)別是,DivideByZeroException是檢查型(checked)異常,而ArithmeticException是非檢查(unchecked)型異常。由于ArithmeticException是非檢查型異常,一個(gè)方法就算會(huì)拋出該異常,也不必在其throw子句中聲明它。任何Error或RuntimeException異常的子類異常都是非檢查型異常。(ArithmeticException就是RuntimeException的子類。)通過捕獲ArithmeticException和拋出DivideByZeroException,remainder()方法強(qiáng)迫它的調(diào)用者去處理除數(shù)為0的可能性,要么捕獲它,要么在其throw子句中聲明DivideByZeroException異常。這是因?yàn)椋馜ivideByZeroException這種在方法中拋出的檢查型異常,要么在方法中捕獲,要么在其throw子句中聲明,二者必選其一。而像ArithmeticException這種非檢查型異常,就不需要去顯式捕獲和聲明。
javac為remainder()方法生成的字節(jié)碼序列如下:
-
// The main bytecode sequence for remainder:
-
0 iload_0 // Push local variable 0 (arg passed as divisor)
-
1 iload_1 // Push local variable 1 (arg passed as dividend)
-
2 irem // Pop divisor, pop dividend, push remainder
-
3 ireturn // Return int on top of stack (the remainder)
-
// The bytecode sequence for the catch (ArithmeticException) clause:
-
4 pop // Pop the reference to the ArithmeticException
-
// because it is not used by this catch clause.
-
5 new #5 < Class DivideByZeroException >
-
// Create and push reference to new object of class
-
// DivideByZeroException.
-
8 dup // Duplicate the reference to the new
-
// object on the top of the stack because it
-
// must be both initialized
-
// and thrown. The initialization will consume
-
// the copy of the reference created by the dup.
-
9 invokenonvirtual #9 < Method DivideByZeroException.< init >()V >
-
// Call the constructor for the DivideByZeroException
-
// to initialize it. This instruction
-
// will pop the top reference to the object.
-
12 athrow // Pop the reference to a Throwable object, in this
-
// case the DivideByZeroException,
-
// and throw the exception.
remainder()方法的字節(jié)碼有2個(gè)單獨(dú)的部分。第一部分是該方法的正常執(zhí)行路徑,這部分從第0行開始,到第3行結(jié)束。第二部分是從第4行開始,到12行結(jié)束的catch子句。
主字節(jié)碼序列中的irem指令可能會(huì)拋出ArithmeticException異常。如果異常發(fā)生了,JVM通過在異常表中查找匹配的異常,它會(huì)知道要跳轉(zhuǎn)到相應(yīng)的異常處理的catch子句的字節(jié)碼序列部分。每個(gè)捕獲異常的方法,都跟類文件中與方法字節(jié)碼一起交付的異常表關(guān)聯(lián)。每一個(gè)捕獲異常的try塊,都是異常表中的一行。每行4條信息:開始行號(hào)(from)和結(jié)束行號(hào)(to),要跳轉(zhuǎn)的字節(jié)碼序列行號(hào)(target),被捕獲的異常類的常量池索引(type)。remainder()方法的異常表如下所示:
FROM
|
TO
|
TARGET
|
TYPE
|
---|---|---|---|
0 | 4 | 4 | < Class java.lang.ArithmeticException > |
上面的異常表表明,行號(hào)1到3范圍內(nèi),ArithmeticException將被捕獲。異常表中的“to”下面的結(jié)束行號(hào)始終比異常捕獲的最大行號(hào)大1,上表中,結(jié)束行號(hào)為4,而異常捕獲的最大行號(hào)是3。行號(hào)0到3的字節(jié)碼序列對(duì)應(yīng)remainder()方法中的try塊。“target”列中,是行0到3的字節(jié)碼發(fā)生ArithmeticException異常時(shí)要跳轉(zhuǎn)到的目標(biāo)行號(hào)。
如果方法執(zhí)行過程中產(chǎn)生了異常,JVM會(huì)在異常表中查找匹配行。異常表中的匹配行要符合下面的條件:當(dāng)前pc寄存器的值要在該行的表示范圍之內(nèi),[from, to),且產(chǎn)生的異常是該行所指定的異常類或其子類。JVM按從上到下的次序查找異常表。當(dāng)找到了第一個(gè)匹配行,JVM把pc寄存器設(shè)為新的跳轉(zhuǎn)行號(hào),從此行繼續(xù)往下執(zhí)行。如果找不到匹配行,JVM彈出當(dāng)前棧幀,并重新拋出同一個(gè)異常。當(dāng)JVM彈出當(dāng)前棧幀時(shí),它會(huì)終止當(dāng)前方法的執(zhí)行,返回到調(diào)用該方法的上一個(gè)方法那里。這時(shí),在上一個(gè)方法里,并不會(huì)繼續(xù)正常的執(zhí)行過程,而是拋出同樣的異常,促使JVM重新查找該方法的異常表。
Java程序員可以用throw語句拋出像remainder()方法的catch子句中的異常,DivideByZeroException。下表列出了拋出異常的字節(jié)碼:
OPCODE
|
OPERAND(S)
|
DESCRIPTION
|
---|---|---|
athrow | (none) | pops Throwable object reference, throws the exception |
athrow指令把棧頂元素彈出,該元素必須是Throwable的子類或其自身的對(duì)象引用,而拋出的異常類型由棧頂彈出的對(duì)象引用所指明。
本文譯自:How the Java virtual machine handles exceptions
本文出自:碼農(nóng)合作社 》JVM中的異常處理,轉(zhuǎn)載請(qǐng)注明。
posted on 2014-06-02 03:54 Rolandz 閱讀(2773) 評(píng)論(0) 編輯 收藏 所屬分類: 編程實(shí)踐