背景
刚看了极客时间关于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。
| 12
 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获得的字节码内容:
| 12
 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.ExceptionTestCompiled 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,字节码会有什么变化:
| 12
 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获取的字节码:
| 12
 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.ExceptionTestCompiled 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块内直接返回,则程序无法执行到抛出异常这步操作就已经被返回了。