欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-16 23:40:10
...

参考文档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个字节,用来表示类的访问权限或者类的属性(是否是枚举、注解、抽象类等),取值如下图所示
    [JVM] class字节码文件结构分析
  • 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种数据类型及其结构信息

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

字段信息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文件如下

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

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

魔数Magic Number

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

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

主版本号和次版本号

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

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

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

常量池Constant Pool

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

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

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

cp_info {
    u1 tag;
    u1 info[];
}

第一个常量

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

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

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)

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

结合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

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

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

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

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

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

索引37对应的常量项为

#37 = Utf8               Hello World

而常量池第二个常量为

#2 = String             #37            // Hello World

也对的上。

第三个常量

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

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

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;

刚好对应

第四个常量项

第四个常量字节码如下

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

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

相关标签: jvm java