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

我有这个代码 –

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

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

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

try-catch块的成本

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

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

如何在HotSpot中抛出exception?

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

好吧,’如何在汇编级别捕获exception?’

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

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

 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 

您会注意到Exception table 。 此构造指示VM,如果类型type的exception发生在fromto的指令之间,那么它必须goto指令(偏移) target 。 它还指示它在堆栈上推送Exception引用,以便可以复制其值并将其绑定到catch块中的参数。

您还有与上面的throw语句相关的这篇文章。

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

指令可以执行以下操作

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

JVM解释了会发生什么

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

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

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

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

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

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

我没有给你一个明确的答案,但我会为你提供组装的步骤,你可以根据你的用例进行剖析。

  • 确保将您感兴趣的方法编译为汇编
    • 我通常使用2 for循环来破坏逃逸分析,并确保我的代码没有标记为NOP
  • 确保您正在运行调试JVM构建或已构建HotSpot反汇编程序 – 在Mac上构建的说明 。
  • 使用java -XX运行程序:+ UnlockDiagnosticVMOptions -XX:+ PrintAssembly
    • 准备好看一些优化的代码 。

还有一个用于分析和可视化JIT编译器日志文件的GUI工具,它被称为JITWatch 。

这是我用它来测试它的类,可能有点冗长但是将myMethod和doSomething都编译成汇编。

 public class Question { public static void main(String[] args) { long result = 0; for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { result += myMethod(); } } System.out.println(result); } private static long myMethod() { try { return doSomething(); } catch (Exception e) { return 100; } } private static long doSomething() { if (System.currentTimeMillis() % 2 == 0) return System.currentTimeMillis(); else throw new RuntimeException(); } } 

我会引用你的堆栈溢出问题的答案 。

对于在这些机器上运行的程序,java字节代码是机器语言。 没有“汇编语言”。