作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。

一、字节码的整体结构

  • 使用javap -verbose命令分析一个字节码文件时,将会分析该字节码的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息。
  • 如果想要解析字节码中的私有方法必须加上-p参数:javap -verbose -p class文件
  • 也可通过jclassilb这个插件来查看字节码文件

整体结构:
在这里插入图片描述
整体结构的拆分表
在这里插入图片描述
字节码中的两种数据类型:

  • 字节数据直接量:这是基本的数据类型。共细分为U1、U2、U4、U8四种,分别代表练习的1个字节、2个字节、4个字节、8个字节组成的整体数据。
  • 表(数组):表是由多个基本数据或其他表,按照既定的顺序组成的大的数据集合。表是有结构的,它的结构体现在:组成表的成分所在位置和顺序都是已经严格定义好的。

二、字节码范围解析

2.1、魔数

占据第1~4个字节:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE。

2.2、版本信息

占据第5~8字节:前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。这里的版本号为00 00 00 34,换算成十进制,表示此版本号为0,主版本号为52。所以该文件的版本号为1.8.0。可以通过java -version来验证

2.3、常量池(constant pool)

紧接着主版本号之后的就是常量池入口。一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看做是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中的。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java中申明为final的常量值等,而符号引用如类和接口的全局限定名(包名与类名一起的),字段的名称和描述符,方法的名称和描述符等。常量池并非只能存储常量,也可以存储变量

总体结构:

Java类所对应的常量池主要由常量池数量与常量池数组(常量表)这两部分共同组成。

常量池数量:

紧跟着主版本号后面,占据2个字节;常量池数组则紧跟常量池数量之后。

常量池数组:

常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同,但是每一种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节,一共有四种标志位U1、U2、U4、U8分别占1、2、4、8个字节。JVM在解析常量池时,会根据就这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数=常量池数量-1(其中0暂时不适应),目的是满足某些常量池索引值的数据在特定情况下需要表达【不引用任何一个常量池】的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应null值;所以,常量池的所有从1而非0开始。

常量池中11种数据类型的结构表:
在这里插入图片描述

2.4、描述符规则

在JVM中,每个变量/字段都有描述信息,主要用于描述字段的数据类型、方法的参数类别与返回值。如:B – byte,C – char,D – double,F – float,I – int,J – long,S – short,Z – boolean,V – void,L – 对象类型(如Ljava/lang/String),数组 – 每一个维度前置使用[表示。描述方法时描述顺序按照:先参数列表,后返回值的顺序

2.5、访问标志(Access Flag)

访问标志信息占据2个字节,它包括该Class文件是类还是结构,是否被定义成public,是否是abstract,如果是类,是否被声明成final。在字节码中它是并集显示的,如两个字节00 21即:ACC_PUBLIC和ACC_SUPER的并集。

访问标志符分类:
在这里插入图片描述

2.6、类名(This Class)

描述该类的名称在常量池中的索引,占据2个字节。

2.7、父类名(Super Class)

描述该类的父类的名称在常量池中的索引,占据2个字节。

2.8、接口(Interfaces)

描述该类实现了哪些接口,占据2+n个字节,它分为两部分:interface_count(接口个数)2个字节、interfaces(接口名)每个接口名占据2个字节。

注:当我们interface_count为0时则字节码为00 00时,后面一部分(即:interfaces接口名)就不会出现在字节码文件中。

2.9、成员变量(Fields)

描述类和接口中声明了哪些变量,包含了类级别的变量以及实例变量,但是不包括方法内部声明的局部变量。它占据2+n个字节,它分为两部分:field_count(成员变量个数)2个字节、fields(变量表)n个字节。

变量表的结构:

类型 名称 数量
U2 access_flags(访问标志符) 1
U2 name_index(名称在常量池的索引) 1
U2 descriptor_index(类型描述符在常量池的索引) 1
U2 attributes_count(属性数量) 1
attribute_info attributes(属性信息表,属性数为0时则不存在) attributes_count
field_info{
	u2 access_flags;
	u2 name_index;
	u2 descriptor_index;
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

2.10、方法(Methods)

描述类中包含的方法。它占据2+n个字节,它分为两部分:method_count(方法个数)2个字节、methods(方法表)n个字节。

方法表的结构:

类型 名称 数量
U2 access_flags(访问标志符) 1
U2 name_index(名称在常量池的索引) 1
U2 descriptor_index(类型描述符在常量池的索引) 1
U2 attributes_count(属性数量) 1
attribute_info attributes(属性信息表,属性数为0时则不存在) attributes_count
method_info{
	u2 access_flags;
	u2 name_index;
	u2 descriptor_index;
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

2.11、属性表(attribute_info)

  • 用于存放变量以及方法的属性,如:方法中的代码就存储在Code属性表中
  • JVM预定义了部分attribute,但是编译器字节也可以实现字节的attribute写如Class文件里,供运行时使用。
  • 不同的attribute通过attribute_name_index来区分,如我们所知的方法中的属性表(Code属性)、Code属性中的属性表(LineNumberTable属性、LocalVariableTable属性)、字节码文件中的属性表(SourceFile属性)

属性表的结构:

类型 名称 数量
U2 attribute_name_index(属性名索引) 1
U4 attribute_length(属性长度) 1
U1 info(信息表) attribute_length
attribute_info{
	u2 attribute_name_index;
	u4 attribute_length;
	u1 info[attribute_length];
}

2.12、方法表中的Code属性

  • attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段
  • max_stack表示这个方法运行时的任何时刻所能表达的操作数栈的最大深度
  • mac_locals表示方法执行期间创建的局部变量数目,包含用来表示传入的参数的局部变量
  • code_length表示该方法所包含的字节码的字节数以及具体的指令码,具体字节码即是该方法被调用时,虚拟机所执行的字节码
  • exception_table,这里存放的是处理异常的信息,每个exception_table表项由u2 star_pc、end_pc、handler_pc、catch_type组成
  • start_pc和end_pc表示在code数组中的从start_pc到end_pc处(含头不含尾)的指令抛出的异常会由这个表项来处理。
  • handler_pc表示处理异常的代码的开始处。
  • catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有异常。
Code_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
	u2 max_stack;
	u2 max_locals;
	u4 code_length;
	u1 code[code_length];
	u2 exception_table_length;
	{
		u2 star_pc;
		u2 end_pc;
		u2 handler_pc;
		u2 catch_type;
	}exception_table[exception_table_length];
	u2 attribute_count;
	attribute_info attribute[attribute_count]
}

2.13、Code属性中的LineNumberTable属性

  • attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段
  • line_number_table_length表示行号表的总数
  • star_pc表示助记词的行号
  • line_number表示源代码的行号
LineNumberTable_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
	u2 line_number_table_length;
	{
		u2 star_pc;
		u2 line_number;
	}line_number_table[line_number_table_length];
}

2.14、Code属性中的LocalVariableTable属性

  • attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段
  • local_variable_table_length表示局部变量的个数
  • star_pc表示局部变量的开始位置
  • length表示局部变量的结束位置
  • index表示局部变量的名称索引
  • descriptor_index表示局部变量的描述索引
  • index表示StackMapTable,主要用于校验检查
LocalVariableTable_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
	u2 local_variable_table_length;
	{
		u2 star_pc;
		u2 length;
		u2 name_index;
		u2 descriptor_index;
		u2 index;
	}local_variable_table[local_variable_table_length];
}

三、synchronize关键字在字节码中的描述

  • synchronize关键字在字节码中必定会出现moniterenter以及moniterexit来表示进入/退出锁,并且moniterenter只会有一处,而moniterexit会有多处,包含了程序正常执行结束的moniterexit以及异常退出的moniterexit。
  • synchronize的使用方法:1、在实例化方法上使用(表示给当前对象this加锁);2、在静态方法上使用(表示给当前类的所对应的class对象加锁);3、在方法内部使用。

3.1、在实例化方法上使用

  • 源代码:
package com.brycen.trucks.byteclass;

public class MyTest2 {
    ...
    private synchronized void setX(int x){
        this.x = x;
    }
    ...
}

  • 字节码:

由此可以看出synchronized作用于方法上时在code属性中是没有moniterenter、monitexit标识符的,因为synchronized的周期是整个code所以无需显视的显示在code当中了,而是通过访问标志ACC_SYNCHRONIZED来说明

private synchronized void setX(int);
    descriptor: (I)V
    flags: ACC_PRIVATE, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #4                  // Field x:I
         5: return
      LineNumberTable:
        line 27: 0
        line 28: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/ideas/trucks/byteclass/MyTest2;
            0       6     1     x   I
    MethodParameters:
      Name                           Flags
      x

3.2、在静态方法上使用

  • 源代码:
package com.brycen.trucks.byteclass;

public class MyTest2 {
    ...
    private synchronized static void setX(int x){
    }
    ...
}

  • 字节码:

由此可以看出它只是比作用于实例方法上多了一个访问标志ACC_STATIC,它实则是给这个方法所属类的Class对象上的锁

 private static synchronized void setX(int);
    descriptor: (I)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 27: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0     x   I
    MethodParameters:
      Name                           Flags
      x

3.3、在方法内部使用

  • 源代码:
package com.brycen.trucks.byteclass;

public class MyTest2 {
    ...
    private void test(String str){
        synchronized (str){
            System.out.println("hello world");
        }
    }
    ...
}

  • 字节码:

可以看出,此时在code属性中出现了monitorenter以及monitorexit,并且有多个monitorexit,这是因为程序的健壮性,不光在程序正常结束的时候可以monitorexit,也防止出现异常时可以正常的monitorexit释放锁

 private void test(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PRIVATE
    Code:
      stack=2, locals=4, args_size=2
         0: aload_1
         1: dup
         2: astore_2
         3: monitorenter
         4: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #11                 // String hello world
         9: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_2
        13: monitorexit
        14: goto          22
        17: astore_3
        18: aload_2
        19: monitorexit
        20: aload_3
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 30: 0
        line 31: 4
        line 32: 12
        line 33: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lcom/ideas/trucks/byteclass/MyTest2;
            0      23     1   str   Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/brycen/trucks/byteclass/MyTest2, class java/lang/String, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
    MethodParameters:
      Name                           Flags
      str

四、静态变量和变量的赋值时机

  • 在我们类的构造方法<init>内并没有任何代码,而在字节码中,我们可以看到,它却生成了很多代码。这是因为构造方法会对我们类的变量进行赋值操作,而这些操作都会在构造方法里完成。
  • 下面的static代码块实则是<clinit>,它主要是处理静态代码块中的代码以及静态变量赋值的操作
 ...
 public com.ideas.trucks.byteclass.MyTest2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String  Welcome
         7: putfield      #3                  // Field str:Ljava/lang/String;
        10: aload_0
        11: iconst_5
        12: putfield      #4                  // Field x:I
        15: return
      LineNumberTable:
        line 10: 0
        line 12: 4
        line 14: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Lcom/ideas/trucks/byteclass/MyTest2;
...    
...
...
static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        10
         2: invokestatic  #8                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: putstatic     #9                  // Field in:Ljava/lang/Integer;
         8: return
      LineNumberTable:
        line 16: 0
...

五、分析在字节码中的this、异常表

源代码

package com.brycen.demo.byteclass;

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 in = new FileInputStream("text.txt");

            ServerSocket serverSocket = new ServerSocket(9999);
            serverSocket.accept();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("finally");
        }
    }
}

字节码

Classfile /Users/liuyu/IdeaProjects/demo/target/classes/com/brycen/demo/byteclass/MyTest3.class
  Last modified 2021-9-25; size 1276 bytes
  MD5 checksum f06777dfe6bff8db829d9a0650ee19b2
  Compiled from "MyTest3.java"
public class com.brycen.demo.byteclass.MyTest3
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #18.#42        // java/lang/Object."<init>":()V
   #2 = Class              #43            // java/io/FileInputStream
   #3 = String             #44            // text.txt
   #4 = Methodref          #2.#45         // java/io/FileInputStream."<init>":(Ljava/lang/String;)V
   #5 = Class              #46            // java/net/ServerSocket
   #6 = Methodref          #5.#47         // java/net/ServerSocket."<init>":(I)V
   #7 = Methodref          #5.#48         // java/net/ServerSocket.accept:()Ljava/net/Socket;
   #8 = Fieldref           #49.#50        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = String             #51            // finally
  #10 = Methodref          #52.#53        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Class              #54            // java/io/FileNotFoundException
  #12 = Methodref          #11.#55        // java/io/FileNotFoundException.printStackTrace:()V
  #13 = Class              #56            // java/io/IOException
  #14 = Methodref          #13.#55        // java/io/IOException.printStackTrace:()V
  #15 = Class              #57            // java/lang/Exception
  #16 = Methodref          #15.#55        // java/lang/Exception.printStackTrace:()V
  #17 = Class              #58            // com/brycen/demo/byteclass/MyTest3
  #18 = Class              #59            // java/lang/Object
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               LocalVariableTable
  #24 = Utf8               this
  #25 = Utf8               Lcom/brycen/demo/byteclass/MyTest3;
  #26 = Utf8               test
  #27 = Utf8               in
  #28 = Utf8               Ljava/io/InputStream;
  #29 = Utf8               serverSocket
  #30 = Utf8               Ljava/net/ServerSocket;
  #31 = Utf8               e
  #32 = Utf8               Ljava/io/FileNotFoundException;
  #33 = Utf8               Ljava/io/IOException;
  #34 = Utf8               Ljava/lang/Exception;
  #35 = Utf8               StackMapTable
  #36 = Class              #54            // java/io/FileNotFoundException
  #37 = Class              #56            // java/io/IOException
  #38 = Class              #57            // java/lang/Exception
  #39 = Class              #60            // java/lang/Throwable
  #40 = Utf8               SourceFile
  #41 = Utf8               MyTest3.java
  #42 = NameAndType        #19:#20        // "<init>":()V
  #43 = Utf8               java/io/FileInputStream
  #44 = Utf8               text.txt
  #45 = NameAndType        #19:#61        // "<init>":(Ljava/lang/String;)V
  #46 = Utf8               java/net/ServerSocket
  #47 = NameAndType        #19:#62        // "<init>":(I)V
  #48 = NameAndType        #63:#64        // accept:()Ljava/net/Socket;
  #49 = Class              #65            // java/lang/System
  #50 = NameAndType        #66:#67        // out:Ljava/io/PrintStream;
  #51 = Utf8               finally
  #52 = Class              #68            // java/io/PrintStream
  #53 = NameAndType        #69:#61        // println:(Ljava/lang/String;)V
  #54 = Utf8               java/io/FileNotFoundException
  #55 = NameAndType        #70:#20        // printStackTrace:()V
  #56 = Utf8               java/io/IOException
  #57 = Utf8               java/lang/Exception
  #58 = Utf8               com/brycen/demo/byteclass/MyTest3
  #59 = Utf8               java/lang/Object
  #60 = Utf8               java/lang/Throwable
  #61 = Utf8               (Ljava/lang/String;)V
  #62 = Utf8               (I)V
  #63 = Utf8               accept
  #64 = Utf8               ()Ljava/net/Socket;
  #65 = Utf8               java/lang/System
  #66 = Utf8               out
  #67 = Utf8               Ljava/io/PrintStream;
  #68 = Utf8               java/io/PrintStream
  #69 = Utf8               println
  #70 = Utf8               printStackTrace
{
  public com.brycen.demo.byteclass.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/brycen/demo/byteclass/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          96
        37: astore_1
        38: aload_1
        39: invokevirtual #12                 // Method java/io/FileNotFoundException.printStackTrace:()V
        42: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        45: ldc           #9                  // String finally
        47: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        50: goto          96
        53: astore_1
        54: aload_1
        55: invokevirtual #14                 // Method java/io/IOException.printStackTrace:()V
        58: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        61: ldc           #9                  // String finally
        63: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        66: goto          96
        69: astore_1
        70: aload_1
        71: invokevirtual #16                 // Method java/lang/Exception.printStackTrace:()V
        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: goto          96
        85: astore_3
        86: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        89: ldc           #9                  // String finally
        91: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        94: aload_3
        95: athrow
        96: return
      Exception table:
         from    to  target type
             0    26    37   Class java/io/FileNotFoundException
             0    26    53   Class java/io/IOException
             0    26    69   Class java/lang/Exception
             0    26    85   any
            37    42    85   any
            53    58    85   any
            69    74    85   any
      LineNumberTable:
        line 13: 0
        line 15: 10
        line 16: 21
        line 24: 26
        line 25: 34
        line 17: 37
        line 18: 38
        line 24: 42
        line 25: 50
        line 19: 53
        line 20: 54
        line 24: 58
        line 25: 66
        line 21: 69
        line 22: 70
        line 24: 74
        line 25: 82
        line 24: 85
        line 25: 94
        line 26: 96
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10      16     1    in   Ljava/io/InputStream;
           21       5     2 serverSocket   Ljava/net/ServerSocket;
           38       4     1     e   Ljava/io/FileNotFoundException;
           54       4     1     e   Ljava/io/IOException;
           70       4     1     e   Ljava/lang/Exception;
            0      97     0  this   Lcom/brycen/demo/byteclass/MyTest3;
      StackMapTable: number_of_entries = 5
        frame_type = 101 /* same_locals_1_stack_item */
          stack = [ class java/io/FileNotFoundException ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/io/IOException ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 10 /* same */
}
SourceFile: "MyTest3.java"

5.1、实例方法中this的分析

疑问:

为什么在test方法中字节码中的args_size=1,很明显我们并没有传入参数呀?

解答:

对于Java类中的每一个实例方法(非static方法),在其编译后所生成的字节码中,方法参数的数量总是会比源代码中的方法参数多一个(this),它位于方法的第一个参数位置处。这个操作是在编译期间完成的,即由javac编译器在编译阶段自动向实例方法传入该this参数。所以,在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量。

5.2、字节码中test方法中的locals为什么是4个?

从源代码中我们可以看到的局部变量是is、serverSocket和三个ex变量,再加上隐藏的this局部变量应该是6个才对呀,为什么是4个呢?这是因为在三个catch中的,程序最多只会走到一个catch中去。

5.3、字节码中异常表的分析

test方法的code属性表:

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          96
        37: astore_1
        38: aload_1
        39: invokevirtual #12                 // Method java/io/FileNotFoundException.printStackTrace:()V
        42: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        45: ldc           #9                  // String finally
        47: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        50: goto          96
        53: astore_1
        54: aload_1
        55: invokevirtual #14                 // Method java/io/IOException.printStackTrace:()V
        58: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        61: ldc           #9                  // String finally
        63: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        66: goto          96
        69: astore_1
        70: aload_1
        71: invokevirtual #16                 // Method java/lang/Exception.printStackTrace:()V
        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: goto          96
        85: astore_3
        86: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        89: ldc           #9                  // String finally
        91: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        94: aload_3
        95: athrow
        96: return

test方法的异常表:

 Exception table:
         from    to  target type
             0    26    37   Class java/io/FileNotFoundException
             0    26    53   Class java/io/IOException
             0    26    69   Class java/lang/Exception
             0    26    85   any
            37    42    85   any
            53    58    85   any
            69    74    85   any

异常表分析:

我们从异常表中可以看到,助记符的第0行到第25行发生不同异常时都跳转到相应的catch中去,而后面的any则是处理以上三个catch不能处理的异常的以及catch中出现的异常,虽然在程序角度上Exception可以处理所有异常,但是在字节码中它并不这样任务。

程序处理异常的流程:

  • 如果代码中发生FileNotFoundException异常则程序会跳转第37行至50行的catch中,这里我们发现finally中的执行代码也会在其中,然后执行catch中的逻辑随后会goto到96行(即return)
  • 如果代码中发生IOException异常则程序会跳转第53行至66行的catch中,这里我们发现finally中的执行代码也会在其中,然后执行catch中的逻辑随后会goto到96行(即return)
  • 如果代码中发生Exception异常则程序会跳转第69行至82行的catch中,这里我们发现finally中的执行代码也会在其中,然后执行catch中的逻辑随后会goto到96行(即return)

由上面的流程我们可以得知,finally中的代码在编译成字节码的时候会在每个catch执行范围以及正常程序的执行范围都会存在。

注:如果我们在方法上抛出异常,则在字节码中,该方法的code属性中的异常表不会包含该异常。编译器会在与code属性的同一级别中创建一个Exception属性,将抛出的异常表存储在里面


版权声明:本文为liuyu973971883原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/liuyu973971883/article/details/120168986