[JVM] class字节码文件结构分析

参考文档Chapter 4. The class File Format

class字节码文件结构组成

总体结构

java源文件经java编译器编译后生成的class文件整体结构如下图所示:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
  • magic: 魔数,4个字节,为固定值0xCAFEBABE。jvm在加载class文件时,首先检查该class文件的前四个字节是否为0xCAFEBABE,若不是则jvm认为该文件不是java的class文件;
  • minor_version,major_version: 分别占用2个字节,class文件的次版本号和主版本号;
  • constant_pool_count: 2个字节,常量池中常量个数;
  • constant_pool[]: 常量池表,根据常量池项内容的不同,大小也不一样,保存该类中所用到的所有常量,常量池表中每一项的大小都不是固定的,这一点和编程语言中的数组有区别,每一个常量池项的第一个字节用来标识常量池项的类型,不同的类型info具有不同的长度,稍后我们将介绍每一种常量池类型;
  • access_flags: 2个字节,用来表示类的访问权限或者类的属性(是否是枚举、注解、抽象类等),取值如下图所示
    access_flags取值
  • this_class:2个字节,指向常量池中代表该类并且类型为CONSTANT_Class_info的常量池项的索引;
  • super_class: 2个字节,指向常量池中代表该类的父类并且类型为CONSTANT_Class_info的常量池索引(java中只能继承一个类,可以实现多个接口);
  • interfaces_count:2个字节,该类所实现接口的数量;
  • interfaces[interfaces_count]:每个元素两个字节,该类所实现的所有接口,每个元素都是指向常量池的索引;
  • fields_count:2个字节,该类中字段的数量;
  • fields[fields_count]:字段表,每个元素的类型是field_info,字段表中每个元素大小都可能不一样;
  • methods_count:2个字节,类中方法数量;
  • methods[methods_count]:方法表,方法表中每个元素类型都是method_info,2元素的大小可能不一样;
  • attributes_count:2个字节,该类中属性的数量;
  • attributes[attributes_count]:属性表,属性表中元素类型为attribute_info,每个元素大小可能不一样;

常量池信息cp_info

常量池表constant_pool[]中每一项元素具有如下结构

cp_info {
    u1 tag;
    u1 info[];
}

其中第一个字节是tag,标识该常量池元素的类型,如下图所示为常量池元素的11种数据类型及其结构信息

常量池元素结构信息

字段信息field_info

每个field_info中描述了类和接口中声明的一个变量(包括类变量以及实例变量,不包括方法内部声明的局部变量),field_info结构如下所示

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

方法信息method_info

方法表中一个method_into描述了一个方法的信息,method_info具有如下结构

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

其中attribute_infofield_info中的attribute_info

属性信息attribute_info

attribute_info具有如下结构

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

attribute_info用来描述ClassFilefield_infomethod_info中的属性信息。前两个字段是固定的,2个字节的attribute_name_index表示常量池中该属性名称常量的索引,4个字节的attribute_length表示接下来属性内容的长度,根据不同的属性类型info数组所表示的内容也有所不同。如下示例为Code属性结构

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 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

jvm在解析的时候会根据attribute_name_index所指向的属性名称用不同的属性结构来解析info数组,从而得到属性信息。

class字节码结构示例分析

我们以如下所示代码为例分析class文件的字节码结构

public class MyTest2 {

    String str = "Hello World";

    private int x = 5;

    public static Integer in = 10;

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public static void main(String[] args) {
        MyTest2 myTest2 = new MyTest2();

        myTest2.setX(8);

        in = 2;
    }
}

使用javap -verbose com.ctrip.flight.test.jvm.bytecode.MyTest2命令反编译该class文件得到如下结果

Classfile /Users/peter/MyWork/study/IdeaWorkSpace/jvm/out/production/classes/com/ctrip/flight/test/jvm/bytecode/MyTest2.class
  Last modified 2018-8-12; size 944 bytes
  MD5 checksum da374f2c6579a47b4d9e25f99c22559b
  Compiled from "MyTest2.java"
public class com.ctrip.flight.test.jvm.bytecode.MyTest2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#36        // java/lang/Object."<init>":()V
   #2 = String             #37            // Hello World
   #3 = Fieldref           #5.#38         // com/ctrip/flight/test/jvm/bytecode/MyTest2.str:Ljava/lang/String;
   #4 = Fieldref           #5.#39         // com/ctrip/flight/test/jvm/bytecode/MyTest2.x:I
   #5 = Class              #40            // com/ctrip/flight/test/jvm/bytecode/MyTest2
   #6 = Methodref          #5.#36         // com/ctrip/flight/test/jvm/bytecode/MyTest2."<init>":()V
   #7 = Methodref          #5.#41         // com/ctrip/flight/test/jvm/bytecode/MyTest2.setX:(I)V
   #8 = Methodref          #42.#43        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #9 = Fieldref           #5.#44         // com/ctrip/flight/test/jvm/bytecode/MyTest2.in:Ljava/lang/Integer;
  #10 = Class              #45            // java/lang/Object
  #11 = Utf8               str
  #12 = Utf8               Ljava/lang/String;
  #13 = Utf8               x
  #14 = Utf8               I
  #15 = Utf8               in
  #16 = Utf8               Ljava/lang/Integer;
  #17 = Utf8               <init>
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;
  #24 = Utf8               getX
  #25 = Utf8               ()I
  #26 = Utf8               setX
  #27 = Utf8               (I)V
  #28 = Utf8               main
  #29 = Utf8               ([Ljava/lang/String;)V
  #30 = Utf8               args
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               myTest2
  #33 = Utf8               <clinit>
  #34 = Utf8               SourceFile
  #35 = Utf8               MyTest2.java
  #36 = NameAndType        #17:#18        // "<init>":()V
  #37 = Utf8               Hello World
  #38 = NameAndType        #11:#12        // str:Ljava/lang/String;
  #39 = NameAndType        #13:#14        // x:I
  #40 = Utf8               com/ctrip/flight/test/jvm/bytecode/MyTest2
  #41 = NameAndType        #26:#27        // setX:(I)V
  #42 = Class              #46            // java/lang/Integer
  #43 = NameAndType        #47:#48        // valueOf:(I)Ljava/lang/Integer;
  #44 = NameAndType        #15:#16        // in:Ljava/lang/Integer;
  #45 = Utf8               java/lang/Object
  #46 = Utf8               java/lang/Integer
  #47 = Utf8               valueOf
  #48 = Utf8               (I)Ljava/lang/Integer;
{
  java.lang.String str;
    descriptor: Ljava/lang/String;
    flags:

  public static java.lang.Integer in;
    descriptor: Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC

  public com.ctrip.flight.test.jvm.bytecode.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 Hello World
         7: putfield      #3                  // Field str:Ljava/lang/String;
        10: aload_0
        11: iconst_5
        12: putfield      #4                  // Field x:I
        15: return
      LineNumberTable:
        line 3: 0
        line 5: 4
        line 7: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;

  public int getX();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #4                  // Field x:I
         4: ireturn
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;

  public void setX(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #4                  // Field x:I
         5: return
      LineNumberTable:
        line 16: 0
        line 17: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/ctrip/flight/test/jvm/bytecode/MyTest2;
            0       6     1     x   I

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #5                  // class com/ctrip/flight/test/jvm/bytecode/MyTest2
         3: dup
         4: invokespecial #6                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: bipush        8
        11: invokevirtual #7                  // Method setX:(I)V
        14: iconst_2
        15: invokestatic  #8                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        18: putstatic     #9                  // Field in:Ljava/lang/Integer;
        21: return
      LineNumberTable:
        line 20: 0
        line 22: 8
        line 24: 14
        line 25: 21
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      22     0  args   [Ljava/lang/String;
            8      14     1 myTest2   Lcom/ctrip/flight/test/jvm/bytecode/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 9: 0
}
SourceFile: "MyTest2.java"

生成的二进制class文件如下

class文件字节码内容

我们将按照class文件结构一个一个字节的来分析该二进制文件,如下图所示的class文件结构
class字节码文件结构

魔数Magic Number

处于class文件最开始的4个字节,值固定为CAFEBABE,如下图所示

这里写图片描述

主版本号和次版本号

接下来的4个字节是主版本号和次版本号,前两个字节为次版本号,后两个字节为主版本号

class文件版本号

从图中可以看出次版本号为0,主版本号为52(16进制0x34)。如果一个class文件是用高版本的java编译器编译的,如果把该class文件放在低版本的JDK上运行就会抛异常。

常量池Constant Pool

接下来的两个字节是常量池的长度,如下图所示

class文件常量池长度

从图中可以看出常量池长度为49(16进制0x31),也就是接下来常量池表中有49个常量,每个常量的元素大小都不同,因此只能先确定前面一个常量内容后再确定后面一个常量的内容。回顾我们之前说到的常量池项的结构

cp_info {
    u1 tag;
    u1 info[];
}
第一个常量

第一个常量的第一个字节是tag用来标识常量类型,这里第一个常量的第一个字节如下

class文件常量池第一个常量tag字段

tag值为10(16进制的0x0A),常量池类型为 CONSTANT_Methodref,是一个方法信息,结构如下

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

class_index值为10(16进制0x000A),name_and_type_index值为36(16进制的0x0024)

这里写图片描述

结合 javap -verbose得到的结果可知,序号为10和36的常量池项为

 #10 = Class              #45            // java/lang/Object
 #36 = NameAndType        #17:#18        // "<init>":()V

而常量池中第一个常量项为

#1 = Methodref          #10.#36        // java/lang/Object."<init>":()V

结果吻合。

第二个常量

第二个常量tag为8

tag

tag为8的类型为 CONSTANT_String_info,结构如下

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

也就是接下来的两个字节是一个指向字符串常量的索引。string_index值为37(0x0025)

string_index

索引37对应的常量项为

#37 = Utf8               Hello World

而常量池第二个常量为

#2 = String             #37            // Hello World

也对的上。

第三个常量

为简化描述从第三个常量开始我们把该常量所有字段全部标记出来。第三个字节码如下

第三个常量

tag值为9,表示常量类型为 CONSTANT_Fieldref, CONSTANT_Fieldref结构如下所示

CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

因此class_index值为5(0x0005),name_and_type_index值为38(0x0026),对应的常量项为

#5 = Class              #40            // com/ctrip/flight/test/jvm/bytecode/MyTest2
#38 = NameAndType        #11:#12        // str:Ljava/lang/String;

而第三个常量项为

 #3 = Fieldref           #5.#38         // com/ctrip/flight/test/jvm/bytecode/MyTest2.str:Ljava/lang/String;

刚好对应

第四个常量项

第四个常量字节码如下

这里写图片描述

tag值为9,同第三个常量,不再做分析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值