JVM如何保证finally块的执行?

时间:2014-01-07 03:48:53

标签: java exception exception-handling jvm try-catch

这个问题针对的是 JVM如何能够保证finally块的执行(前提是JVM不会崩溃并且线程不会被中断或退出)。

在面试问题的提示下,我试图了解JVM如何确保即使在奇怪的情况下也能执行finally块...请考虑以下代码:

try{

    int[] someArray = new int[10];
    int invalid = someArray[10];
}
catch(IndexOutOfBoundsException e){

    throw new RuntimeException("Other Exception");
}
finally{

    //close open files or HTTP connections etc.
}


虽然这可能是一个奇怪的情况,但仍然保证执行finally块,尽管 Other Exception 没有被明确处理。 JVM如何处理这样的情况?

我的想法:

根据我的理解并且到目前为止已经读过,当遇到未处理的异常时,控制从当前线程转移到我认为的那个线程的ThreadGroupThreadGroup中是否有一些条款可以检查最终需要执行的块?我能想到的唯一另一件事可能是finally块的地址存储在某个地方。然后JVM在检测到异常时执行goto,并在finally块执行完毕后返回异常。

任何人都可以澄清这个过程是如何实际发生的吗?

2 个答案:

答案 0 :(得分:20)

编译这个小程序(我意识到我应该使用你的例子,但没有区别)

public static void main(String[] args) {
    try {
        Float s = Float.parseFloat("0.0327f");
    } finally {
        System.out.println("hello");
    }
}

我用过

>java -version 
java version "1.8.0-ea"  // should be same for 7
Java(TM) SE Runtime Environment (build 1.8.0-ea-b118)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b60, mixed mode)

然后执行

javac -v -c <fully qualified class name>

获取字节码。你会看到像

这样的东西
public static void main(java.lang.String[]);
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
    stack=2, locals=3, args_size=1
       0: ldc           #2                  // String 0.0327f
       2: invokestatic  #3                  // Method java/lang/Float.parseFloat:(Ljava/lang/String;)F
       5: invokestatic  #4                  // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
       8: astore_1
       9: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: ldc           #6                  // String hello
      14: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      17: goto          31
      20: astore_2
      21: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      24: ldc           #6                  // String hello
      26: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      29: aload_2
      30: athrow
      31: return
    Exception table:
       from    to  target type
           0     9    20   any
          20    21    20   any
    LineNumberTable:
      line 10: 0
      line 12: 9
      line 13: 17
      line 12: 20
      line 14: 31
    StackMapTable: number_of_entries = 2
         frame_type = 84 /* same_locals_1_stack_item */
        stack = [ class java/lang/Throwable ]
         frame_type = 10 /* same */

您会注意到finally内的代码出现两次,一次出现在goto之前,一次出现之后。您还会注意到Exception table,它指定在某行发生异常时要转到哪个语句。

因此,如果在语句0-9之间发生任何异常,请转到第20行并在finally之后执行goto内的所有内容。如果没有发生异常,请执行finally,然后执行gotofinally之后跳过goto

在所有情况下,您都将执行finally块内的代码。

  

未明确处理的其他例外

使用finally块,将创建一个Exception table条目,用于处理任何类型的Throwable


Here's a listing of the bytecode instructions.

答案 1 :(得分:5)

我相信这blog清楚地描述了内部:

  

如果方法定义了try-catch或try-finally异常   处理程序然后将创建一个异常表。这包含   每个异常处理程序或finally块的信息,包括   处理程序适用的范围,是什么类型的异常   处理和处理程序代码的位置。

     

抛出异常时   如果没有,JVM会在当前方法中查找匹配的处理程序   发现该方法突然弹出当前堆栈帧并结束   在调用方法中重新抛出异常(新的当前   帧)。如果在所有帧之前没有找到异常处理程序   弹出然后线程终止。这也可能导致JVM   如果在最后一个非守护进程中抛出异常,则自行终止   线程,例如,如果线程是主线程。

     

最后,异常处理程序匹配所有类型的异常等等   每当抛出异常时总是执行。在没有的情况下   异常被抛出一个finally块仍在执行结束时执行   方法,这是通过跳转到finally处理程序代码来实现的   紧接在执行return语句之前。