背景
刚看了极客时间关于JVM异常的相关信息,然后在评论里看到这么一条:
如果finally有return语句,catch内throw的异常会被忽略,这个从jvm层面怎么解释呢?
2018-09-02
作者回复
catch里抛的异常会被finally捕获了,再执行完finally代码后重新抛出该异常。由于finally代码块有个return语句,在重新抛出前就返回了。
你可以利用这篇文章的知识,就着javap的输出,分析一下具体的程序路径
所以就着JVM字节码具体分析了一下两种程序路径:
- catch内抛出异常, finally内存在return
- catch内抛出异常, finally内不存在return
finally内不存在return
先看下正常情况,即:finally内不存在return。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ExceptionTest {
public static void main(String[] args) throws Exception { try { throw new RuntimeException("runtime exception"); } catch (Exception e) { throw new Exception("inner exception"); } finally { System.out.println("finally"); }
}
}
|
通过javap
获得的字节码内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| "C:\Program Files\Java\jdk1.8.0_181\bin\javap.exe" -l -c com.whatakitty.learn.ExceptionTest Compiled from "ExceptionTest.java" public class com.whatakitty.learn.ExceptionTest { public com.whatakitty.learn.ExceptionTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/whatakitty/learn/ExceptionTest;
# 主要查看这一块,这里是main方法的字节码内容 public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: new #2 // class java/lang/RuntimeException 3: dup 4: ldc #3 // String runtime exception 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V # 在这里抛出RuntimeException异常,从异常表内检查to和from的的范围以及对应type的匹配,获得到异常处理的目标为#10 9: athrow # 抛出异常后再这里开始处理RuntimeException异常 10: astore_1 11: new #5 // class java/lang/Exception 14: dup 15: ldc #6 // String inner exception 17: invokespecial #7 // Method java/lang/Exception."<init>":(Ljava/lang/String;)V # 在处理异常的时候,又抛出一个新的Exception异常,同样查找异常表,获取到any异常的匹配,目标为#21 20: athrow # 开始处理未捕获的异常Exception 21: astore_2 22: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 25: ldc #9 // String finally 27: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: aload_2 # 直接将未捕获异常抛出,JVM会在执行完整个方法后,弹出栈帧,然后会处理新的顶层栈帧。这里的话,由于无新的栈帧,程序直接结束 31: athrow # 异常表,所有的捕获异常都会在这里记录 Exception table: from to target type 0 10 10 Class java/lang/Exception 0 22 21 any LineNumberTable: line 11: 0 line 12: 10 line 13: 11 line 15: 21 line 17: 30 LocalVariableTable: Start Length Slot Name Signature 11 10 1 e Ljava/lang/Exception; 0 32 0 args [Ljava/lang/String; }
|
finally内存在return
查看了不存在return的正常情况,现在看一下如果在finally内直接返回return,字节码会有什么变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ExceptionTest {
public static void main(String[] args) throws Exception { try { throw new RuntimeException("runtime exception"); } catch (Exception e) { throw new Exception("inner exception"); } finally { return; }
}
}
|
通过javap
获取的字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| "C:\Program Files\Java\jdk1.8.0_181\bin\javap.exe" -l -c com.whatakitty.learn.ExceptionTest Compiled from "ExceptionTest.java" public class com.whatakitty.learn.ExceptionTest { public com.whatakitty.learn.ExceptionTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/whatakitty/learn/ExceptionTest;
public static void main(java.lang.String[]) throws java.lang.Exception; Code: 0: new #2 // class java/lang/RuntimeException 3: dup 4: ldc #3 // String runtime exception 6: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V 9: athrow 10: astore_1 11: new #5 // class java/lang/Exception 14: dup 15: ldc #6 // String inner exception 17: invokespecial #7 // Method java/lang/Exception."<init>":(Ljava/lang/String;)V # 截止到获取异常并抛出新的Exception异常为止,与前者的流程保持一致。 20: athrow # 根据异常表定位并获取目标#21后开始执行finally块的代码 21: astore_2 # 由于在finally块内直接返回了return,并不会再执行athrow的字节码指令,也就是说: # 内部抛出的Exception异常,已经被JVM忽略 22: return Exception table: from to target type 0 10 10 Class java/lang/Exception 0 22 21 any LineNumberTable: line 11: 0 line 12: 10 line 13: 11 line 15: 21 LocalVariableTable: Start Length Slot Name Signature 11 10 1 e Ljava/lang/Exception; 0 23 0 args [Ljava/lang/String; }
|
总结
按照正常的顺序逻辑,如果出现未捕获异常,则会在finally后执行完成后抛出这个异常。但是,如果在finally块内直接返回,则程序无法执行到抛出异常这步操作就已经被返回了。