您的位置:首页 > 编程语言 > Java开发

(JavaCard) JVM的异常控制器原理,以及编译器对finally的特殊处理

2010-04-29 17:06 363 查看
鉴于此处的机制,在卡端和上层大同小异,以上层代码作为例子。

JVM == JCVM

JRE == JCRE

JVM对异常的处理主要是基于异常表(Exception Table),每个包含了try的方法在编译后除字节码外,都会产生一个附加的数据结构--异常表,

异常表结构:

{

{PC:BEGIN, PC:END, PC:HANDLER, EXCEPTION-TYPE}

.................

}

PC:BEGIN-PC:END 代表着异常发生的PC值的范围

EXCEPTION-TYPE 代表着异常捕获的类型

PC:HANDLER 代表着异常处理的指针

异常表的产生:

异常表是多组数据,每个catch都会产生一个记录。举个例子

class MyException extends Exception {MyException(String str){super(str)}}

class TestException {

static void f() throws MyException{
throw err;
}
static void g() throws MyException{
try {
f();
}
catch(MyException e) {

int a = 0; //pc = 6
}

catch(Exception e) {

int a = 0; //pc = 10
}

}

方法f是可能产生异常的函数,在方法g中使用了try/catch进行了调用。

此时g产生的异常表为:

Exception Table:
[pc: 0, pc: 3] -> 6 when : MyException
[pc: 0, pc: 3] -> 10 when : Exception

异常表的使用:

当JVM的函数产生异常后(athrow指令):

1.JVM会在当前Frame的Exception Table里面逐条查找PC范围和异常类型符合*的记录。并将PC跳转到对应的异常处理的位置上。

注意:类型符合不是完全匹配,而是符合向上原则,即父类的catch可以捕获子类的对象即 e instance of CLASS。

2.如果当前Frame不存在Exception Table, 或者在Exception Table里找不到匹配的记录,则当前Frame出栈,把当前Frame设置成上一个Frame,获得新的当前pc和当前异常表。

3.继续执行(1),直到JAVA STACK到达栈底还不能匹配,则异常未处理,抛出到JRE。

finally的特殊处理。

我们知道java有个关键字finally,这个关键字在Javacard里面也是支持的。此关键字的作用是不管异常是否被catch到,都要执行到。具体下来,就有几种情况:
1.异常没有被catch到。
2.异常被catch到,并处理。
3.异常被catch到,但又被rethrow了。
结合上面的关于异常表的理解,我们很容易困惑finally在流程上是怎么保证在以上三种情况下执行的。JVM是否要为finally的这种情况作出特殊处理。经过分析后,我欣慰的发现java的编译器已经在字节码层解决了这个问题(虽然方法相当的笨重)
Java编译器把finally认为是一个小的inline函数,实施的是指令替换策略,我们用下面的典型例子对照字节码来测试一下。

代码如下:
class MyException extends Exception {
MyException(String str) {
super(str);
}
}

public class TestException {
static void Mod() {}
static void Mod1() {}
static void Mod2() {}
static void Mod3() {}

static void f() throws MyException {
throw new MyException("");
}

static void g() throws Exception {
try {
f();
Mod();
} catch (MyException e) {
Mod1();

} catch (Exception e) {
Mod2();
throw e;
} finally {
Mod3();
}
}

public static void main(String[] args) {

try {
g();
} catch (Exception e) {
}
}
}

其中方法g的字节码如下:
// Method descriptor #6 ()V
// Stack: 1, Locals: 2
static void g() throws java.lang.Exception;
0 invokestatic TestException.f() : void [30]
3 invokestatic TestException.Mod() : void [32]
6 goto 31
9 astore_0 [e]
10 invokestatic TestException.Mod1() : void [34]
13 invokestatic TestException.Mod3() : void [36]
16 goto 34
19 astore_0 [e]
20 invokestatic TestException.Mod2() : void [38]
23 aload_0 [e]
24 athrow
25 astore_1
26 invokestatic TestException.Mod3() : void [36]
29 aload_1
30 athrow
31 invokestatic TestException.Mod3() : void [36]
34 return
Exception Table:
[pc: 0, pc: 6] -> 9 when : MyException
[pc: 0, pc: 6] -> 19 when : java.lang.Exception
[pc: 0, pc: 13] -> 25 when : any
[pc: 19, pc: 25] -> 25 when : any

可以看到,在增加了finally关键字后,finally内的过程被作为内联函数复制到Exception Table增加了两个类型为any的记录。
流程为:
1.当方法f没有抛出异常的时候,
执行完try块后,goto 31。此处的finally代码被直接编译,顺序执行。
3.当方法f抛出了catch不支持的类型异常的时候,
异常处理器分析Exception Table, 得到匹配记录 [pc: 0, pc: 13] -> 25 when : any 捕获了异常
pc跳转到25,在复制了finally段字节码后,复制捕获的异常再次抛出( 30 athrow)。
而这个新异常则不能在Exception Table里面得到匹配,被直接抛往栈的上层。
2.当方法f抛出了MyException类型的异常后,
异常处理器分析Exception Table,得到匹配记录 [pc: 0, pc: 6] -> 9 when : MyException 捕获了异常
pc跳转到9执行catch(MyException e)段的内容,并将finally内的字节码复制到catch段的字节码后,执行后跳转到结束。
3.当方法f抛出了Exception类型的异常后
异常处理器分析Exception Table, 得到匹配记录[pc: 0, pc: 6] -> 19 when : java.lang.Exception 捕获了异常
pc跳转到19执行catch(Exception e)段的内容,由于段内rethrow了异常(24 athrow)再次出发异常处理器
异常处理器分析Exception Table,得到匹配记录[pc: 19, pc: 25] -> 25 when : any 捕获了异常
pc跳转到25在复制了finally段字节码后,复制捕获的异常再次抛出( 30 athrow)。

综合以上流程,字节码的处理完全符合标准的异常处理流程JVM并不需要为此机制作出改动。编译器通过内联函数和扩展异常表来实现的这个过程。
可以这么理解finally,
再有异常发生的时候,可以理解为一个可以捕获方法内任何异常(包含try块中的异常和catch块中的异常)并自动重新抛出的catch段。
在没有异常,或者异常被捕获的过程,以字节码内联的方式将段内容自动添加到对应的段后。

同样基于这个了解,如果finally段内如果有return关键字的话,异常已经被捕获,在再次抛出之前,return导致了Frame的出栈,从而导致后面的(athrow)没有执行,JVM的异常状态神奇的消失了。所以在代码实现上面,一定要避免这种有违异常约定的写法。
(现在java编译器已经对此种语法给出警告:(finally block does not complete normally)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: