• 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"
  • 相关阅读:
    net core体系-2继续认识net core
    net core体系-1概要
    iOS开发UI篇—懒加载
    iOS开发UI篇—简单的浏览器查看程序
    iOS开发UI篇—transframe属性(形变)
    iOS开发UI基础—手写控件,frame,center和bounds属性
    iOS开发UI篇—Button基础
    Foundation框架—集合
    Foundation框架—字符串
    Foundation框架—结构体
  • 原文地址:https://www.cnblogs.com/wxdlut/p/14103063.html
Copyright © 2020-2023  润新知