• JVM字节码(五)


    这一节,我们来分析下异常在字节码文件中的表现,我们来看一下MyTest3:

    package com.leolin.jvm.bytecode;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    
    public class MyTest3 {
        public void test() {
            try {
                InputStream is = new FileInputStream("text.txt");
                ServerSocket serverSocket = new ServerSocket(9999);
                serverSocket.accept();
            } catch (FileNotFoundException ex) {
            } catch (IOException ex) {
            } catch (Exception ex) {
            } finally {
                System.out.println("finally");
            }
        }
    }
    

      

    上面的代码比较简单,就不解释了,我们来看下反编译之后的字节码:

    D:Fjava_spacejvm-lecture	argetclasses>javap -verbose com.leolin.jvm.bytecode.MyTest3
    Classfile /D:/F/java_space/jvm-lecture/target/classes/com/leolin/jvm/bytecode/MyTest3.class
      Last modified 2020-5-15; size 1066 bytes
      MD5 checksum 68544433621c8ea9d9ba3aa9638949a6
      Compiled from "MyTest3.java"
    public class com.leolin.jvm.bytecode.MyTest3
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #15.#35        // java/lang/Object."<init>":()V
       #2 = Class              #36            // java/io/FileInputStream
       #3 = String             #37            // text.txt
       #4 = Methodref          #2.#38         // java/io/FileInputStream."<init>":(Ljava/lang/String;)V
       #5 = Class              #39            // java/net/ServerSocket
       #6 = Methodref          #5.#40         // java/net/ServerSocket."<init>":(I)V
       #7 = Methodref          #5.#41         // java/net/ServerSocket.accept:()Ljava/net/Socket;
       #8 = Fieldref           #42.#43        // java/lang/System.out:Ljava/io/PrintStream;
       #9 = String             #44            // finally
      #10 = Methodref          #45.#46        // java/io/PrintStream.println:(Ljava/lang/String;)V
      #11 = Class              #47            // java/io/FileNotFoundException
      #12 = Class              #48            // java/io/IOException
      #13 = Class              #49            // java/lang/Exception
      #14 = Class              #50            // com/leolin/jvm/bytecode/MyTest3
      #15 = Class              #51            // java/lang/Object
      #16 = Utf8               <init>
      #17 = Utf8               ()V
      #18 = Utf8               Code
      #19 = Utf8               LineNumberTable
      #20 = Utf8               LocalVariableTable
      #21 = Utf8               this
      #22 = Utf8               Lcom/leolin/jvm/bytecode/MyTest3;
      #23 = Utf8               test
      #24 = Utf8               is
      #25 = Utf8               Ljava/io/InputStream;
      #26 = Utf8               serverSocket
      #27 = Utf8               Ljava/net/ServerSocket;
      #28 = Utf8               StackMapTable
      #29 = Class              #47            // java/io/FileNotFoundException
      #30 = Class              #48            // java/io/IOException
      #31 = Class              #49            // java/lang/Exception
      #32 = Class              #52            // java/lang/Throwable
      #33 = Utf8               SourceFile
      #34 = Utf8               MyTest3.java
      #35 = NameAndType        #16:#17        // "<init>":()V
      #36 = Utf8               java/io/FileInputStream
      #37 = Utf8               text.txt
      #38 = NameAndType        #16:#53        // "<init>":(Ljava/lang/String;)V
      #39 = Utf8               java/net/ServerSocket
      #40 = NameAndType        #16:#54        // "<init>":(I)V
      #41 = NameAndType        #55:#56        // accept:()Ljava/net/Socket;
      #42 = Class              #57            // java/lang/System
      #43 = NameAndType        #58:#59        // out:Ljava/io/PrintStream;
      #44 = Utf8               finally
      #45 = Class              #60            // java/io/PrintStream
      #46 = NameAndType        #61:#53        // println:(Ljava/lang/String;)V
      #47 = Utf8               java/io/FileNotFoundException
      #48 = Utf8               java/io/IOException
      #49 = Utf8               java/lang/Exception
      #50 = Utf8               com/leolin/jvm/bytecode/MyTest3
      #51 = Utf8               java/lang/Object
      #52 = Utf8               java/lang/Throwable
      #53 = Utf8               (Ljava/lang/String;)V
      #54 = Utf8               (I)V
      #55 = Utf8               accept
      #56 = Utf8               ()Ljava/net/Socket;
      #57 = Utf8               java/lang/System
      #58 = Utf8               out
      #59 = Utf8               Ljava/io/PrintStream;
      #60 = Utf8               java/io/PrintStream
      #61 = Utf8               println
    {
      public com.leolin.jvm.bytecode.MyTest3();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 9: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/leolin/jvm/bytecode/MyTest3;
    
      public void test();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=4, args_size=1
             0: new           #2                  // class java/io/FileInputStream
             3: dup
             4: ldc           #3                  // String text.txt
             6: invokespecial #4                  // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
             9: astore_1
            10: new           #5                  // class java/net/ServerSocket
            13: dup
            14: sipush        9999
            17: invokespecial #6                  // Method java/net/ServerSocket."<init>":(I)V
            20: astore_2
            21: aload_2
            22: invokevirtual #7                  // Method java/net/ServerSocket.accept:()Ljava/net/Socket;
            25: pop
            26: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
            29: ldc           #9                  // String finally
            31: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            34: goto          84
            37: astore_1
            38: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
            41: ldc           #9                  // String finally
            43: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            46: goto          84
            49: astore_1
            50: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
            53: ldc           #9                  // String finally
            55: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            58: goto          84
            61: astore_1
            62: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
            65: ldc           #9                  // String finally
            67: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            70: goto          84
            73: astore_3
            74: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
            77: ldc           #9                  // String finally
            79: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            82: aload_3
            83: athrow
            84: return
          Exception table:
             from    to  target type
                 0    26    37   Class java/io/FileNotFoundException
                 0    26    49   Class java/io/IOException
                 0    26    61   Class java/lang/Exception
                 0    26    73   any
          LineNumberTable:
            line 12: 0
            line 13: 10
            line 14: 21
            line 19: 26
            line 20: 34
            line 15: 37
            line 19: 38
            line 20: 46
            line 16: 49
            line 19: 50
            line 20: 58
            line 17: 61
            line 19: 62
            line 20: 70
            line 19: 73
            line 21: 84
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
               10      16     1    is   Ljava/io/InputStream;
               21       5     2 serverSocket   Ljava/net/ServerSocket;
                0      85     0  this   Lcom/leolin/jvm/bytecode/MyTest3;
          StackMapTable: number_of_entries = 5
            frame_type = 101 /* same_locals_1_stack_item */
              stack = [ class java/io/FileNotFoundException ]
            frame_type = 75 /* same_locals_1_stack_item */
              stack = [ class java/io/IOException ]
            frame_type = 75 /* same_locals_1_stack_item */
              stack = [ class java/lang/Exception ]
            frame_type = 75 /* same_locals_1_stack_item */
              stack = [ class java/lang/Throwable ]
            frame_type = 10 /* same */
    }
    SourceFile: "MyTest3.java"
    

      

    我们专注在test()方法的Code部分,可以看到,局部变量有4个,而test()的局部变量只有3个。除了我们主动声明的is和serverSocket变量、Java帮我们隐式声明的this变量,这里还多出一个变量,是什么呢?不要忘了,我们还声明了异常对象ex,只是异常对象的具体类型只有在运行期才能确认,所以不在局部变量表中。

    在偏移0到22,对应源代码的12到14行。由于这些指令之前都解释过,这里就不再解释了。在偏移22的指令执行完毕之后,原先serverSocket.accept()会将一个Socket对象压入栈中,由于在我们源代码里没有引用这个对象,所以又调用pop指令将这个对象弹出。之后偏移26到31行对应的是源代码的finally块,打印完finally之后就调用34行的goto语句,跳到84行return,整个程序结束。这是正常执行时候的逻辑,但是如果发生了异常呢?

    我们来看异常表(Exception Table),这张表有四列:from、to、target和type,每一行的意思代表从偏移from到to(不包含to)的指令,有可能抛出对应type的异常,到异常发生时,需要跳转到偏移target的位置。以第一行为例:从偏移0到偏移26(不包含26),即0到25之间的指令,有可能抛出FileNotFoundException异常,当异常发生时,跳转到偏移37的位置处理异常,同理第二行和第三行的IOException和Exception异常。这里多出一个any,表示处理上面无法处理的异常,一般我们会人为Exception能处理所有的异常,但字节码并不这么认为,所以这里会多出一个any,这是编译器为我们自动生成。

    如果我们从37、49、61的指令开始处,都是执行astore_1,这里是把异常对象赋值给局部变量表索引为1的位置,也许会有人疑问局部变量表索引1的位置不是存储is变量吗?其类型是InputStream。其实这种看法只是从Java语法层面上来看,异常对象和InputStream对象是两种不同的类型,但从字节码层面上来看,这两个对象都只是引用而已,而且一旦到达catch块,就意味着is变量的引用已经失效,所以我们可以复用索引1的位置,避免额外的空间开销。

    如果我们从每个catch块对应的指令往下看,每个catch块都会跟着finally块中编译后的指令。这意味着在字节码层面,实现finally功能实际上就是在程序正常执行完毕,和在程序抛出异常处理异常之后,都会冗余一块finally代码块对应的指令,以实现不管程序是正常执行完毕还是抛出异常,finally块的代码始终都会执行的效果。

    现在,我们不修改test方法内部的代码,不过我们在方法声明处抛出IOException、FileNotFoundException和NullPointerException这三种异常,NullPointerException是运行期异常,一般不需要特别声明,不过这里我们为了做实验特地声明一下:

    public void test() throws IOException, FileNotFoundException, NullPointerException {
        ……
    }
    

      

    我们重新编译MyTest3,看看反编译之后的test()方法:

    public void test() throws java.io.IOException, java.io.FileNotFoundException, java.lang.NullPointerException
    ;
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          ……
        Exceptions:
          throws java.io.IOException, java.io.FileNotFoundException, java.lang.NullPointerException
    

      

    Code内容其实和之前一样,不过这里多了一个Exceptions,和Code平级,里面存储我们在test()方法声明处所抛出的异常。 

  • 相关阅读:
    2019hdu多校1
    codefroce842C
    [codeforce686D]树的重心
    [codeforce1188C&D]
    Educational Codeforces Round 66
    [hdu4343]interval query
    Luogu 4234 最小差值生成树
    BZOJ 2594 水管局长
    Luogu 2173 [ZJOI2012]网络
    Luogu 2147 洞穴勘测
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/12893257.html
Copyright © 2020-2023  润新知