如何在低(程序集)级别捕获和处理异常?

2022-09-02 10:03:02

我有这个代码 -

try {
     doSomething();
} catch (Exception e) {
   e.printStackTrace();
}

编译器将如何实际实现。在生成的程序集代码中实际检查异常的位置?

更新
我知道上面的代码是如何转换为字节码的。字节码仅将 try-catch 转换为相应的 try-handler 块。我对如何将其转换为程序集/或由jvm处理感兴趣。


答案 1

如果我正确理解了您的问题,则以下代码

public class Example {
    public static void main(String[] args) {
        try {
            otherMethod();
        }
        catch (Exception e) {}
        try {
            otherMethod();
            someMethod();
        }
        catch (SQLException e) {}
        catch (IOException e) {}
    }

    public static void someMethod() throws IOException {throw new IOException();}
    public static void otherMethod() throws SQLException, IOException {}
}

产生以下(人类可读版本的摘录)字节码。

//  main method
     0: invokestatic  #2                  // Method otherMethod:()V
     3: goto          7
     6: astore_1      
     7: invokestatic  #2                  // Method otherMethod:()V
    10: invokestatic  #4                  // Method someMethod:()V
    13: goto          21
    16: astore_1      
    17: goto          21
    20: astore_1      
    21: return        
  Exception table:
     from    to  target type
         0     3     6   Class java/lang/Exception
         7    13    16   Class java/sql/SQLException
         7    13    20   Class java/io/IOException

您会注意到 .此构造指示 VM,如果从 到 的指令之间发生类型异常,则它必须指令(偏移)。它还指示它在堆栈上推送引用,以便可以复制其值并将其绑定到块中的参数。Exception tabletypefromtogototargetExceptioncatch

你也有这篇文章与上面的陈述有关。throw

// someMethod method
     0: new           #6                  // class java/io/IOException
     3: dup           
     4: invokespecial #7                  // Method java/io/IOException."<init>":()V
     7: athrow        

指令 athrow 执行以下操作

引发错误或异常(请注意,堆栈的其余部分已被清除,只留下对 Throwable 的引用)

JVM 解释了发生了什么

objectref 必须是引用类型,并且必须引用作为类 Throwable 或 Throwable 子类的实例的对象。它是从操作数堆栈中弹出的。然后,通过在当前方法 (§2.6) 中搜索与 objectref 类匹配的第一个异常处理程序(如 §2.10 中的算法所给出的那样),将引发 objectref。

如果找到与 objectref 匹配的异常处理程序,则它包含用于处理此异常的代码的位置。将 pc 寄存器重置到该位置,清除当前帧的操作数堆栈,将 objectref 推回操作数堆栈,并继续执行。

如果在当前帧中找不到匹配的异常处理程序,则会弹出该帧。如果当前帧表示对同步方法的调用,则在调用该方法时输入或重新进入的监视器将退出,就好像执行监视器exit指令 (§monitorexit) 一样。最后,如果存在这样的帧,则恢复其调用程序的帧,并且对象引用被重击。如果不存在这样的帧,则当前线程退出。

因此,堆栈帧不断被弹出,直到找到一个可以处理抛出异常的帧。

编译器将如何实际实现。在生成的程序集代码中实际检查异常的位置?

编译器生成上面的字节码。没有检查异常,只有字节码指令。将指示 VM 执行我们称之为引发异常的任务,这将导致弹出堆栈、在当前堆栈帧中搜索异常表等。athrow


答案 2

试捕块的成本

粗略地说,块不会向结果程序集添加任何异常检查代码。只要不引发异常,它基本上是无操作的。所有缓慢的工作都是通过异常抛出代码完成的。try

当 进行 JIT 编译时,将在代码之外添加一个异常表。它将可能发生已处理异常的地址范围映射到相应异常处理程序的地址。注意:这些不是字节码索引,而是真正的内存地址。try-catch

如何在 HotSpot 中引发异常?

  1. 隐式异常:并在信号处理程序内部检测到,以响应分段错误。NullPointerExceptionStackOverflowError
  2. ArrayIndexOutOfBoundsException等已明确检查。相应的检查将内联到完成数组访问的已编译代码中。ClassCastException
  3. OutOfMemoryError并且每当执行线程状态转换(vm->java 或 native->java)时,都会显式检查从本机代码引发的所有其他异常。
  4. 由字节码引发的所有用户异常。在快速路径中(当存在同一帧中的处理程序时),JIT 编译为简单的跳转。否则,将发生取消优化,并在 VM 运行时内完成异常处理。athrowcatchathrow

好吧,“如何在装配级别捕获异常?

绝不是。
我的意思是,异常通常不会在程序集级别捕获 - 所有繁重的东西(堆栈行走,处理程序查找,去优化,监视器解锁等)都是在VM运行时中完成的,即在C代码中。


推荐