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

鉴于此处的机制,在卡端和上层大同小异,以上层代码作为例子。

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)

你可能感兴趣的:(jvm,exception,table,Class,编译器,Descriptor)