JVM 如何保证最终块的执行?

这个问题旨在讨论JVM如何能够保证最终块的执行(前提是JVM不会崩溃并且线程不会中断或退出)。

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

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.
}


虽然这可能是一种奇怪的情况,但即使没有显式处理其他异常,最终的块仍然保证被执行。JVM 如何处理这种情况?

我的想法:

根据我到目前为止的理解和阅读,当遇到未处理的异常时,控制权将从当前线程转移到当前线程(我认为是该线程)。中是否有一些规定来检查需要执行的最终区块?我唯一能想到的另一件事是,也许最终块的地址存储在某个地方。然后,JVM 在检测到异常时执行转到,并在最终块完成执行时返回到异常。ThreadGroupThreadGroup

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


答案 1

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

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 */

您会注意到里面的代码最终出现了两次,一次在之前,一次在之后。您还将注意到,如果某个行发生异常,则它指定要转到的语句。gotoException table

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

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

其他未显式处理的异常

使用块,将创建一个条目,该条目将处理任何类型的.finallyException tableThrowable


下面是字节码指令的列表。


答案 2

我相信这个博客清楚地描述了内部:

如果方法定义了 try-catch 或 try-finally 异常处理程序,则将创建一个异常表。这包含每个异常处理程序或最终块的信息,包括处理程序应用的范围,正在处理的异常类型以及处理程序代码的位置。

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

最后,异常处理程序匹配所有类型的异常,因此每当引发异常时始终执行。在没有引发异常的情况下,最终块仍然在方法结束时执行,这是通过在执行 return 语句之前立即跳转到 final 处理程序代码来实现的。


推荐