JVM是如何处理异常的

 

参考:

抛出异常   https://www.liaoxuefeng.com/wiki/1252599548343744/1264738764506656 

JVM 字节码指令手册 - 查看 Java 字节码

 

  今天我介绍了 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"

 

posted on 2020-12-08 15:49  gogoy  阅读(160)  评论(0编辑  收藏  举报

导航