【JVM】类文件结构

2017-07-09  本文已影响48人  maxwellyue

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目按照顺序紧凑地排列在Class文件中,中间没有任何分隔符。

使用命令javac将.java 文件编译为.class文件
使用命令javap输出.class文件的字节码内容

Class文件格式采用类似于C语言结构体的伪结构,只有两种数据类型:无符号数和表。

名称 类型 数量
magic u4 1
minor_version u2 1
major_version u2 1
constant_pool_count u2 1
constant_pool cp_info constant_pool_count - 1
access_flags u2 1
this_class u2 1
super_class u2 1
interfaces_count u2 1
interfaces u2 interfaces_count
fields_count u2 1
fields field_info fields_count
methods_count u2 1
methods method_info methods_count
attributes_count u2 1
attributes attribute_info attributes_count

学习类文件结构,就是要明白上表中各个数据项的具体含义。


魔数与Class文件版本


常量池

常量池是Class文件中的资源仓库,它是Class文件结构中与其他数据项关联最多的数据类型,也是占用Class文件空间最大的数据项之一。

常量类型 表类型 tag取值
UTF-8编码的字符串 CANSTANT_Utf8_info 1
整型字面量 CANSTANT_Integer_info 3
浮点型字面量 CANSTANT_Float_info 4
长整型字面量 CANSTANT_Long_info 5
双精度浮点型字面量 CANSTANT_Double_info 6
类或接口的符号引用 CANSTANT_Class_info 7
字符串类型字面量 CANSTANT_String_info 8
字段的符号引用 CANSTANT_Fieldref_info 9
类中方法的符号引用 CANSTANT_Methodref_info 10
接口中方法的符号引用 CANSTANT_IntefaceMethodref_info 11
字段或方法的部分符号引用 CANSTANT_NameAndType_info 12
方法句柄 CANSTANT_MethodHandle_info 15
方法类型 CANSTANT_MethodType_info 16
动态方法调用点 CANSTANT_InvokeDynamic_info 18

访问标志

即Class文件数据项表中的access_flags,用于识别类或者接口层次的访问信息:该是类还是接口,是否为public,是否定义为abstract;如果是类,是否声明为final;该类是否由用户代码产生,是否为注解,是否为枚举等信息。


类索引、父类索引和接口索引

Class文件根据这三项数据来确定这个类的继承关系。

其中,类索引和父索引各自指向一个常量池中的类型为CANSTANT_Class_info的常量,通过该常量的索引值再定位到常量池中的CANSTANT_Utf8_info类型的常量表示的全限定名字符串。


字段表集合

从概念上理解,字段表集合 = fields[fields_count],用于描述该Class文件对应的代码中声明的变量:包括类级变量以及实例变量,但不包括方法内声明的局部变量。
对于一个字段,描述信息主要有:作用域(public、private、protected),是实例变量还是类变量(有无static修饰),可变性(final),并发可见性(volatile),可否被序列化(transient),数据类型(基本类型、对象、数组),名称。这些信息中各个修饰符都是布尔值(要么有,要么没有),用标志位表示,而名称、数据类型则引用常量池中的常量来描述。
每一个字段会对应一个字段表,字段表的最终结构如下。

名称 类型 数量
access_flag u2 1
name_index u2 1
descriptor_index u2 1
attributes_count u2 1
attributes attribute_info attributes_count

字段表集合中不会列出从超类或父接口中继承而来的字段,但有可能会列出原本Java代码中不存在的字段,比如在内部类中为了保持对外类的访问性,在编译Class文件的时候会自动添加外部类的实例字段。


方法表集合

从概念上理解,方法表集合 = methods[methods_count]。每一个方法对应一个方法表method_info。方法表与字段表的结构一致,只是具体的信息项不同。

名称 类型 数量
access_flag u2 1
name_index u2 1
descriptor_index u2 1
attributes_count u2 1
attributes attribute_info attributes_count

其中,访问信息access_flag包括是否public、private、protected、static、final、synchronized、native、abstract、strictfp、是否接受不定参数、是否由编译器自动产生等。
其中,方法描述符descriptor_index中描述了方法的参数列表(数量、类型、顺序)和返回值。

而方法体中的代码,经过编译器编译成字节码指令后,存储在方法属性表attributes[attributes_count]中。

如果子类没有重写父类的方法,则子类的方法表集合中不会出现来自父类的方法信息,但可能会出现编译器自动添加的方法,如类构造器<clinit>方法和实例构造器<init>方法。


属性表集合

在Class文件、字段表、方法表中都可以包含自己的属性表集合,以此描述某些场景专有的信息。

1、Code属性

Java程序方法体中的代码经过javac编译器编译之后,最终变为字节码指令存储在Code属性表中,即Code属性表是方法表的一部分。但并非所有的方法表都存在该属性,比如接口或者抽象类中的抽象方法就不存在Code属性表。Code属性表的结构如下。

名称 类型 数量
attribute_name_index u2 1
attribute_length u4 1
max_stack u2 1
max_locals u2 1
code_length u4 1
code u1 code_length
exception_table_length u2 1
exception_table exception_info exception_length
attributes_count u2 1
attributes attribute_info attributes_count

Code属性是Class文件中最重要的一个属性,如果把一个Java程序的信息分为代码和元数据两部分,那么在整个Class文件中,Code属性用于描述代码,所有的其他数据项目都用于描述元数据。

2、Exceptions属性

属于方法表中的一部分。作用是列出出方法中可能抛出的受检查异常,也就是方法描述时在throws 关键字后面列出的异常。

3、LineNumberTable属性

属于Code属性的一部分。用于描述Java源代码行号与字节码行号之间的对应关系,它并不是运行时必须的属性,默认生成,可以在javac命令中使用 -g:none-g:lines属性取消生成该信息。不生成该信息对程序运行的影响:当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。

4、LocalVariableTable属性

属于Code属性的一部分。用于描述帧栈中局部变量表中的变量与Java源代码定义的变量之间的关系,不是运行时必须的属性,默认生成,可以在javac命令中使用 -g:none-g:vars属性取消生成该信息。不生成该信息的影响:当其他人引用该方法时,所有的参数名称都会丢失,IDE会使用类似arg0、arg1等占位符代替原有的参数名,给代码编写带来不便。

5、ConstantValue属性

属于字段表中的一部分。作用是通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量(类变量)才可以使用该属性。虚拟机对类变量和实例变量的赋值方式和时机有所不同。对于实例变量的赋值是在实例构造器<init>方法中进行的。对于类变量,如果是同时有static和final修饰的基本类型数据或String类型数据,则会生成ConstantValue属性来进行初始化,否则在<clinit>方法中进行初始化。


实例分析

定义一个父类Animal,两个接口Eat、Sleep,一个要分析的Rabbit类。

package constructor;
public class Animal {

    protected String weight;

    public String getWeight() {
        return weight;
    }

    public void setWeight(String weight) {
        this.weight = weight;
    }
}
package constructor;
public interface Sleep {
    void sleep();
}
package constructor;
public interface Eat {
    void eat();
}
package constructor;
public class Rabbit extends Animal implements Eat, Sleep{

    private String nickName;

    private int age;

    public static final boolean isCute = true;

    @Override
    public void eat() {
        System.out.println("I eat grass");
    }

    @Override
    public void sleep() {
        System.out.println("I sleep well");
    }

    public String play(int temperature){
        if(temperature > 10){
            return "I want to play outside";
        }else {
            return "I want to stay at home";
        }
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

使用命令javac *.java编译所有源文件,生成class文件。(如果仅仅单独编译Rabbit.java文件会提示找不到类Sleep、Eat和Animal等)。
使用javap -verbose Rabbit.class命令查看Rabbit.class文件的字节码内容。

Classfile /Users/yue/Documents/workspace/idea/datacenter/src/test/constructor/Rabbit.class
  Last modified 2017-7-9; size 1092 bytes
  MD5 checksum 64e6283bb3c70e9c41fb2f72e09aae13
  Compiled from "Rabbit.java"
public class constructor.Rabbit extends constructor.Animal implements constructor.Eat,constructor.Sleep
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#41        // constructor/Animal."<init>":()V
   #2 = Fieldref           #42.#43        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #44            // I eat grass
   #4 = Methodref          #45.#46        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #47            // I sleep well
   #6 = String             #48            // I want to play outside
   #7 = String             #49            // I want to stay at home
   #8 = Fieldref           #10.#50        // constructor/Rabbit.nickName:Ljava/lang/String;
   #9 = Fieldref           #10.#51        // constructor/Rabbit.age:I
  #10 = Class              #52            // constructor/Rabbit
  #11 = Class              #53            // constructor/Animal
  #12 = Class              #54            // constructor/Eat
  #13 = Class              #55            // constructor/Sleep
  #14 = Utf8               nickName
  #15 = Utf8               Ljava/lang/String;
  #16 = Utf8               age
  #17 = Utf8               I
  #18 = Utf8               isCute
  #19 = Utf8               Z
  #20 = Utf8               ConstantValue
  #21 = Integer            1
  #22 = Utf8               <init>
  #23 = Utf8               ()V
  #24 = Utf8               Code
  #25 = Utf8               LineNumberTable
  #26 = Utf8               eat
  #27 = Utf8               sleep
  #28 = Utf8               play
  #29 = Utf8               (I)Ljava/lang/String;
  #30 = Utf8               StackMapTable
  #31 = Utf8               getNickName
  #32 = Utf8               ()Ljava/lang/String;
  #33 = Utf8               setNickName
  #34 = Utf8               (Ljava/lang/String;)V
  #35 = Utf8               getAge
  #36 = Utf8               ()I
  #37 = Utf8               setAge
  #38 = Utf8               (I)V
  #39 = Utf8               SourceFile
  #40 = Utf8               Rabbit.java
  #41 = NameAndType        #22:#23        // "<init>":()V
  #42 = Class              #56            // java/lang/System
  #43 = NameAndType        #57:#58        // out:Ljava/io/PrintStream;
  #44 = Utf8               I eat grass
  #45 = Class              #59            // java/io/PrintStream
  #46 = NameAndType        #60:#34        // println:(Ljava/lang/String;)V
  #47 = Utf8               I sleep well
  #48 = Utf8               I want to play outside
  #49 = Utf8               I want to stay at home
  #50 = NameAndType        #14:#15        // nickName:Ljava/lang/String;
  #51 = NameAndType        #16:#17        // age:I
  #52 = Utf8               constructor/Rabbit
  #53 = Utf8               constructor/Animal
  #54 = Utf8               constructor/Eat
  #55 = Utf8               constructor/Sleep
  #56 = Utf8               java/lang/System
  #57 = Utf8               out
  #58 = Utf8               Ljava/io/PrintStream;
  #59 = Utf8               java/io/PrintStream
  #60 = Utf8               println
{
  public static final boolean isCute;
    descriptor: Z
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

  public constructor.Rabbit();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method constructor/Animal."<init>":()V
         4: return
      LineNumberTable:
        line 11: 0

  public void eat();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String I eat grass
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 21: 0
        line 22: 8

  public void sleep();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String I sleep well
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 26: 0
        line 27: 8

  public java.lang.String play(int);
    descriptor: (I)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1
         1: bipush        10
         3: if_icmple     9
         6: ldc           #6                  // String I want to play outside
         8: areturn
         9: ldc           #7                  // String I want to stay at home
        11: areturn
      LineNumberTable:
        line 30: 0
        line 31: 6
        line 33: 9
      StackMapTable: number_of_entries = 1
        frame_type = 9 /* same */

  public java.lang.String getNickName();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #8                  // Field nickName:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 38: 0

  public void setNickName(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #8                  // Field nickName:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 42: 0
        line 43: 5

  public int getAge();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #9                  // Field age:I
         4: ireturn
      LineNumberTable:
        line 46: 0

  public void setAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #9                  // Field age:I
         5: return
      LineNumberTable:
        line 50: 0
        line 51: 5
}
SourceFile: "Rabbit.java"

内容摘抄自《深入理解Java虚拟机》

上一篇下一篇

猜你喜欢

热点阅读