如何在低(汇编)级别捕获和处理异常?

pde*_*eva 16 java jit code-generation

我有这个代码 -

try {
     doSomething();
} catch (Exception e) {
   e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

这将如何实际由编译器实现.实际放入汇编代码生成的异常检查在哪里?

更新
我知道上面的代码如何转换为字节码.字节码仅将try-catch转换为相应的try-handler块.我感兴趣的是它将如何转换为汇编/并由jvm处理.

Sot*_*lis 12

如果我正确理解您的问题,请使用以下代码

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 {}
}
Run Code Online (Sandbox Code Playgroud)

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

//  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
Run Code Online (Sandbox Code Playgroud)

你会注意到的Exception table.这个结构指示VM,如果type在来自fromto 的指令之间发生类型异常to,那么它必须goto指令(偏移)target.它还指示它Exception在堆栈上推送引用,以便可以复制其值并将其绑定到catch块中的参数.

您还有与上述throw声明相关的这篇文章.

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

该指令athrow执行以下操作

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

JVM解释了会发生什么

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

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

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

因此,堆栈帧会一直弹出,直到找到一个可以处理抛出的异常的帧.

这将如何实际由编译器实现.实际放入汇编代码生成的异常检查在哪里?

编译器生成上面的字节码.没有检查异常,只检查字节代码指令.在athrow将指示虚拟机执行的就是我们所说的任务抛出一个异常,这将导致在出栈,当前栈帧等搜索异常表


apa*_*gin 8

try-catch块的成本

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

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

如何在HotSpot中抛出异常?

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

好吧,'如何在汇编级别捕获异常?'

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

  • @pdeva一个很好的问题.与异常表类似,JIT还为每个编译的方法生成一个`ScopeDesc`表.此结构包含调试信息,它将地址范围从已编译的代码映射到虚拟堆栈帧.编译的方法可以具有多个虚拟堆栈帧(用于内联方法).它们在JIT编译期间记录,然后从"ScopeDesc"恢复以构建堆栈跟踪. (3认同)