参考:
抛出异常 https://www.liaoxuefeng.com/wiki/1252599548343744/1264738764506656
今天我介绍了 Java 虚拟机的异常处理机制。Java 的异常分为 Exception 和 Error 两种,而 Exception 又分为 RuntimeException 和其他类型。RuntimeException 和 Error 属于非检查异常。其他的 Exception 皆属于检查异常,在触发时需要显式捕获,或者在方法头用 throws 关键字声明。Java 字节码中,每个方法对应一个异常表。当程序触发异常时,Java 虚拟机将查找异常表,并依此决定需要将控制流转移至哪个异常处理器之中。Java 代码中的 catch 代码块和 finally 代码块都会生成异常表条目。Java 7 引入了 Suppressed 异常、try-with-resources,以及多异常捕获。后两者属于语法糖,能够极大地精简我们的代码。
例子:
1 package exceptio; 2 3 4 public class Foo { 5 private int tryBlock; 6 private int catchBlock; 7 private int finallyBlock; 8 private int methodExit; 9 10 public void test() { 11 try { 12 tryBlock = 0; 13 } catch (Exception e) { 14 catchBlock = 1; 15 } finally { 16 finallyBlock = 2; 17 } 18 methodExit = 3; 19 } 20 21 22 public void test2() { 23 try { 24 tryBlock = 10; 25 } catch (Exception e) { 26 catchBlock = 11; 27 } 28 } 29 30 31 public void test3() { 32 methodExit = 13; 33 } 34 }
当程序触发异常时,Java 虚拟机会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某个异常表条目的监控范围内,Java 虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码。
如果遍历完所有异常表条目,Java 虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的 Java 栈帧,并且在调用者(caller)中重复上述操作。在最坏情况下,Java 虚拟机需要遍历当前线程 Java 栈上所有方法的异常表。
finally 代码块的编译比较复杂。当前版本 Java 编译器的做法,是复制 finally 代码块的内容,分别放在 try-catch 代码块所有正常执行路径以及异常执行路径的出口中。
针对异常执行路径,Java 编译器会生成一个或多个异常表条目,监控整个 try-catch 代码块,并且捕获所有种类的异常(在 javap 中以 any 指代)。这些异常表条目的 target 指针将指向另一份复制的 finally 代码块。并且,在这个 finally 代码块的最后,Java 编译器会重新抛出所捕获的异常。
可以看到,编译结果包含三份 finally 代码块。其中,前两份分别位于 try 代码块和 catch 代码块的正常执行路径出口。最后一份则作为异常处理器,监控 try 代码块以及 catch 代码块。它将捕获 try 代码块触发的、未被 catch 代码块捕获的异常,以及 catch 代码块触发的异常。
这里有一个小问题,如果 catch 代码块捕获了异常,并且触发了另一个异常,那么 finally 捕获并且重抛的异常是哪个呢?答案是后者。也就是说原本的异常便会被忽略掉,这对于代码调试来说十分不利。
javap -v -p Foo.class
1 Classfile /x/testDemo/src/main/java/exceptio/Foo.class 2 Last modified 2020-12-16; size 706 bytes 3 MD5 checksum da3eb9b32015951077d4d9a9440558dc 4 Compiled from "Foo.java" 5 public class exceptio.Foo 6 minor version: 0 7 major version: 52 8 flags: ACC_PUBLIC, ACC_SUPER 9 Constant pool: 10 #1 = Methodref #8.#26 // java/lang/Object."<init>":()V 11 #2 = Fieldref #7.#27 // exceptio/Foo.tryBlock:I 12 #3 = Fieldref #7.#28 // exceptio/Foo.finallyBlock:I 13 #4 = Class #29 // java/lang/Exception 14 #5 = Fieldref #7.#30 // exceptio/Foo.catchBlock:I 15 #6 = Fieldref #7.#31 // exceptio/Foo.methodExit:I 16 #7 = Class #32 // exceptio/Foo 17 #8 = Class #33 // java/lang/Object 18 #9 = Utf8 tryBlock 19 #10 = Utf8 I 20 #11 = Utf8 catchBlock 21 #12 = Utf8 finallyBlock 22 #13 = Utf8 methodExit 23 #14 = Utf8 <init> 24 #15 = Utf8 ()V 25 #16 = Utf8 Code 26 #17 = Utf8 LineNumberTable 27 #18 = Utf8 test 28 #19 = Utf8 StackMapTable 29 #20 = Class #29 // java/lang/Exception 30 #21 = Class #34 // java/lang/Throwable 31 #22 = Utf8 test2 32 #23 = Utf8 test3 33 #24 = Utf8 SourceFile 34 #25 = Utf8 Foo.java 35 #26 = NameAndType #14:#15 // "<init>":()V 36 #27 = NameAndType #9:#10 // tryBlock:I 37 #28 = NameAndType #12:#10 // finallyBlock:I 38 #29 = Utf8 java/lang/Exception 39 #30 = NameAndType #11:#10 // catchBlock:I 40 #31 = NameAndType #13:#10 // methodExit:I 41 #32 = Utf8 exceptio/Foo 42 #33 = Utf8 java/lang/Object 43 #34 = Utf8 java/lang/Throwable 44 { 45 private int tryBlock; 46 descriptor: I 47 flags: ACC_PRIVATE 48 49 private int catchBlock; 50 descriptor: I 51 flags: ACC_PRIVATE 52 53 private int finallyBlock; 54 descriptor: I 55 flags: ACC_PRIVATE 56 57 private int methodExit; 58 descriptor: I 59 flags: ACC_PRIVATE 60 61 public exceptio.Foo(); 62 descriptor: ()V 63 flags: ACC_PUBLIC 64 Code: 65 stack=1, locals=1, args_size=1 66 0: aload_0 67 1: invokespecial #1 // Method java/lang/Object."<init>":()V 68 4: return 69 LineNumberTable: 70 line 4: 0 71 72 public void test(); 73 descriptor: ()V 74 flags: ACC_PUBLIC 75 Code: 76 stack=2, locals=3, args_size=1 77 0: aload_0 78 1: iconst_0 79 2: putfield #2 // Field tryBlock:I 80 5: aload_0 81 6: iconst_2 82 7: putfield #3 // Field finallyBlock:I 83 10: goto 35 84 13: astore_1 85 14: aload_0 86 15: iconst_1 87 16: putfield #5 // Field catchBlock:I 88 19: aload_0 89 20: iconst_2 90 21: putfield #3 // Field finallyBlock:I 91 24: goto 35 92 27: astore_2 93 28: aload_0 94 29: iconst_2 95 30: putfield #3 // Field finallyBlock:I 96 33: aload_2 97 34: athrow 98 35: aload_0 99 36: iconst_3 100 37: putfield #6 // Field methodExit:I 101 40: return 102 Exception table: // 有try、catch、finally,则异常表信息如下 103 from to target type 104 0 5 13 Class java/lang/Exception // 如果try里面代码的异常 被catch(Exception e)捕获,则执行第13行 105 0 5 27 any // 如果try里面代码的异常,未被catch捕获,则执行第27行(finally会捕获any异常) 106 13 19 27 any // 如果catch里面的代码异常,也执行第27行(finally会捕获any异常) 107 LineNumberTable: 108 line 12: 0 109 line 16: 5 110 line 17: 10 111 line 13: 13 112 line 14: 14 113 line 16: 19 114 line 17: 24 115 line 16: 27 116 line 18: 35 117 line 19: 40 118 StackMapTable: number_of_entries = 3 119 frame_type = 77 /* same_locals_1_stack_item */ 120 stack = [ class java/lang/Exception ] 121 frame_type = 77 /* same_locals_1_stack_item */ 122 stack = [ class java/lang/Throwable ] 123 frame_type = 7 /* same */ 124 125 public void test2(); 126 descriptor: ()V 127 flags: ACC_PUBLIC 128 Code: 129 stack=2, locals=2, args_size=1 130 0: aload_0 131 1: bipush 10 132 3: putfield #2 // Field tryBlock:I 133 6: goto 16 134 9: astore_1 135 10: aload_0 136 11: bipush 11 137 13: putfield #5 // Field catchBlock:I 138 16: return 139 Exception table: // 只有try、catch,异常表只能到catch的指定类型,其它无法捕获的类型将抛出给上层方法 140 from to target type 141 0 6 9 Class java/lang/Exception 142 LineNumberTable: 143 line 24: 0 144 line 27: 6 145 line 25: 9 146 line 26: 10 147 line 28: 16 148 StackMapTable: number_of_entries = 2 149 frame_type = 73 /* same_locals_1_stack_item */ 150 stack = [ class java/lang/Exception ] 151 frame_type = 6 /* same */ 152 153 public void test3(); // 无try、catch,因此无异常表 154 descriptor: ()V 155 flags: ACC_PUBLIC 156 Code: 157 stack=2, locals=1, args_size=1 158 0: aload_0 159 1: bipush 13 160 3: putfield #6 // Field methodExit:I 161 6: return 162 LineNumberTable: 163 line 32: 0 164 line 33: 6 165 } 166 SourceFile: "Foo.java"
极客时间的javap结果:
1 $ javap -c Foo 2 ... 3 public void test(); 4 Code: 5 0: aload_0 6 1: iconst_0 7 2: putfield #20 // Field tryBlock:I 8 5: goto 30 9 8: astore_1 10 9: aload_0 11 10: iconst_1 12 11: putfield #22 // Field catchBlock:I 13 14: aload_0 14 15: iconst_2 15 16: putfield #24 // Field finallyBlock:I 16 19: goto 35 17 22: astore_2 18 23: aload_0 19 24: iconst_2 20 25: putfield #24 // Field finallyBlock:I 21 28: aload_2 22 29: athrow 23 30: aload_0 24 31: iconst_2 25 32: putfield #24 // Field finallyBlock:I 26 35: aload_0 27 36: iconst_3 28 37: putfield #26 // Field methodExit:I 29 40: return 30 Exception table: 31 from to target type 32 0 5 8 Class java/lang/Exception 33 0 14 22 any 34 35 ...
例1:在catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常
1 package exception1; 2 3 4 // 在catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常 5 public class Main { 6 public static void main(String[] args) { 7 try { 8 Integer.parseInt("abc"); 9 } catch (Exception e) { 10 System.out.println("catched"); 11 throw new RuntimeException(e); 12 } finally { 13 System.out.println("finally"); 14 } 15 } 16 }
结果:
1 catched 2 finally 3 Exception in thread "main" java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "abc" 4 at exception1.Main.main(Main.java:11) 5 Caused by: java.lang.NumberFormatException: For input string: "abc" 6 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 7 at java.lang.Integer.parseInt(Integer.java:580) 8 at java.lang.Integer.parseInt(Integer.java:615) 9 at exception1.Main.main(Main.java:8) 10 11 Process finished with exit code 1
例2:说明finally抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)
1 package exception2; 2 3 4 /** 5 * 说明finally抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。 6 * 没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。 7 */ 8 public class Main { 9 public static void main(String[] args) { 10 try { 11 Integer.parseInt("abc"); 12 } catch (Exception e) { 13 System.out.println("catched"); 14 throw new RuntimeException(e); 15 } finally { 16 System.out.println("finally"); 17 throw new IllegalArgumentException(); 18 } 19 } 20 }
结果:
1 catched 2 finally 3 Exception in thread "main" java.lang.IllegalArgumentException 4 at exception2.Main.main(Main.java:17) 5 6 Process finished with exit code 1
例3:在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin
变量保存原始异常,然后调用Throwable.addSuppressed()
,把原始异常添加进来,最后在finally
抛出:
1 package exceptio; 2 3 public class Main { 4 public static void main(String[] args) { 5 6 Exception origin = null; 7 8 try { 9 Integer.parseInt("abc"); 10 } catch (Exception e) { 11 System.out.println("catched"); 12 13 origin = e; 14 throw new RuntimeException(e); 15 } finally { 16 System.out.println("finally"); 17 // 如果注释了25~29行,即使catch块里抛出异常,第4行main函数的throws Exception 也不需要,因为这时finally 18 // 是作为一个最终的异常处理器;如果放开25~29行,那么因为finally块自己抛出异常,所以main函数需要trows Exception 19 20 // 因为字节码里的最后一个finally块是作为最终的异常处理器, 21 // 它捕获(1)try触发未被catch捕获到的异常 (2)catch代码块触发的异常 22 23 24 // Exception e = new IllegalArgumentException(); 25 // if (origin != null) { 26 // e.addSuppressed(origin); 27 // } 28 // throw e; 29 } 30 } 31 }
结果:
1 catched 2 finally 3 Exception in thread "main" java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "abc" 4 at exceptio.Main.main(Main.java:14) 5 Caused by: java.lang.NumberFormatException: For input string: "abc" 6 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 7 at java.lang.Integer.parseInt(Integer.java:580) 8 at java.lang.Integer.parseInt(Integer.java:615) 9 at exceptio.Main.main(Main.java:9) 10 11 Process finished with exit code 1
例4:
1 package exception; 2 3 // 编译并用javap -c查看编译后的字节码 4 public class Foo { 5 private int tryBlock; 6 private int catchBlock; 7 private int finallyBlock; 8 private int methodExit; 9 10 public void test() { 11 for (int i = 0; i < 100; i++) { 12 try { 13 tryBlock = 0; 14 if (i < 50) { 15 continue; 16 } else if (i < 80) { 17 break; 18 } else { 19 return; 20 } 21 } catch (Exception e) { 22 catchBlock = 1; 23 } finally { 24 finallyBlock = 2; 25 } 26 } 27 methodExit = 3; 28 } 29 }
javap -v -p Foo.class
1 $ javap -v -p Foo.class 2 Classfile /Users/wuxiong.wx/work/code/testDemo/src/main/java/exception/Foo.class 3 Last modified 2020-12-11; size 769 bytes 4 MD5 checksum 34fc9f363420bd0b9f36de34bdc7ebe2 5 Compiled from "Foo.java" 6 public class exception.Foo 7 minor version: 0 8 major version: 52 9 flags: ACC_PUBLIC, ACC_SUPER 10 Constant pool: 11 #1 = Methodref #8.#30 // java/lang/Object."<init>":()V 12 #2 = Fieldref #7.#31 // exception/Foo.tryBlock:I 13 #3 = Fieldref #7.#32 // exception/Foo.finallyBlock:I 14 #4 = Class #33 // java/lang/Exception 15 #5 = Fieldref #7.#34 // exception/Foo.catchBlock:I 16 #6 = Fieldref #7.#35 // exception/Foo.methodExit:I 17 #7 = Class #36 // exception/Foo 18 #8 = Class #37 // java/lang/Object 19 #9 = Utf8 tryBlock 20 #10 = Utf8 I 21 #11 = Utf8 catchBlock 22 #12 = Utf8 finallyBlock 23 #13 = Utf8 methodExit 24 #14 = Utf8 <init> 25 #15 = Utf8 ()V 26 #16 = Utf8 Code 27 #17 = Utf8 LineNumberTable 28 #18 = Utf8 LocalVariableTable 29 #19 = Utf8 this 30 #20 = Utf8 Lexception/Foo; 31 #21 = Utf8 test 32 #22 = Utf8 e 33 #23 = Utf8 Ljava/lang/Exception; 34 #24 = Utf8 i 35 #25 = Utf8 StackMapTable 36 #26 = Class #33 // java/lang/Exception 37 #27 = Class #38 // java/lang/Throwable 38 #28 = Utf8 SourceFile 39 #29 = Utf8 Foo.java 40 #30 = NameAndType #14:#15 // "<init>":()V 41 #31 = NameAndType #9:#10 // tryBlock:I 42 #32 = NameAndType #12:#10 // finallyBlock:I 43 #33 = Utf8 java/lang/Exception 44 #34 = NameAndType #11:#10 // catchBlock:I 45 #35 = NameAndType #13:#10 // methodExit:I 46 #36 = Utf8 exception/Foo 47 #37 = Utf8 java/lang/Object 48 #38 = Utf8 java/lang/Throwable 49 { 50 private int tryBlock; 51 descriptor: I 52 flags: ACC_PRIVATE 53 54 private int catchBlock; 55 descriptor: I 56 flags: ACC_PRIVATE 57 58 private int finallyBlock; 59 descriptor: I 60 flags: ACC_PRIVATE 61 62 private int methodExit; 63 descriptor: I 64 flags: ACC_PRIVATE 65 66 public exception.Foo(); 67 descriptor: ()V 68 flags: ACC_PUBLIC 69 Code: 70 stack=1, locals=1, args_size=1 71 0: aload_0 72 1: invokespecial #1 // Method java/lang/Object."<init>":()V 73 4: return 74 LineNumberTable: 75 line 4: 0 76 LocalVariableTable: 77 Start Length Slot Name Signature 78 0 5 0 this Lexception/Foo; 79 80 public void test(); 81 descriptor: ()V 82 flags: ACC_PUBLIC 83 Code: 84 stack=2, locals=4, args_size=1 85 0: iconst_0 // 这两行:for 循环,i=0 86 1: istore_1 87 2: iload_1 88 3: bipush 100 89 5: if_icmpge 75 // for循环的比较条件,是否<75 90 8: aload_0 91 9: iconst_0 92 10: putfield #2 // Field tryBlock:I 93 13: iload_1 94 14: bipush 50 95 16: if_icmpge 27 96 19: aload_0 97 20: iconst_2 98 21: putfield #3 // Field finallyBlock:I 99 24: goto 69 100 27: iload_1 101 28: bipush 80 102 30: if_icmpge 41 103 33: aload_0 104 34: iconst_2 105 35: putfield #3 // Field finallyBlock:I 106 38: goto 75 107 41: aload_0 108 42: iconst_2 109 43: putfield #3 // Field finallyBlock:I 110 46: return 111 47: astore_2 112 48: aload_0 113 49: iconst_1 114 50: putfield #5 // Field catchBlock:I 115 53: aload_0 116 54: iconst_2 117 55: putfield #3 // Field finallyBlock:I 118 58: goto 69 119 61: astore_3 120 62: aload_0 121 63: iconst_2 122 64: putfield #3 // Field finallyBlock:I 123 67: aload_3 124 68: athrow 125 69: iinc 1, 1 126 72: goto 2 127 75: aload_0 128 76: iconst_3 129 77: putfield #6 // Field methodExit:I 130 80: return 131 Exception table: 132 from to target type 133 8 19 47 Class java/lang/Exception 134 27 33 47 Class java/lang/Exception 135 8 19 61 any 136 27 33 61 any 137 47 53 61 any 138 LineNumberTable: 139 line 11: 0 140 line 13: 8 141 line 14: 13 142 line 24: 19 143 line 16: 27 144 line 24: 33 145 line 19: 46 146 line 21: 47 147 line 22: 48 148 line 24: 53 149 line 25: 58 150 line 24: 61 151 line 11: 69 152 line 27: 75 153 line 28: 80 154 LocalVariableTable: 155 Start Length Slot Name Signature 156 48 5 2 e Ljava/lang/Exception; 157 2 73 1 i I 158 0 81 0 this Lexception/Foo; 159 StackMapTable: number_of_entries = 7 160 frame_type = 252 /* append */ 161 offset_delta = 2 162 locals = [ int ] 163 frame_type = 24 /* same */ 164 frame_type = 13 /* same */ 165 frame_type = 69 /* same_locals_1_stack_item */ 166 stack = [ class java/lang/Exception ] 167 frame_type = 77 /* same_locals_1_stack_item */ 168 stack = [ class java/lang/Throwable ] 169 frame_type = 7 /* same */ 170 frame_type = 250 /* chop */ 171 offset_delta = 5 172 } 173 SourceFile: "Foo.java"