深入理解JVM - Class类文件的结构

Class文件是Java虚拟机执行引擎的数据入口,也是Java技术体系的基础支柱之一。

Class文件本质

Class文件本质上是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。

Class文件格式

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

  • 无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
  • 表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。

无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的“集合”。

文件格式

类型 名称 数量 描述
u4 magic 1 魔数(魔法数字),表明当前文件是.class文件,固定0xCAFEBABE
u2 minor_version 1 副版本号
u2 major_version 1 主版本号,从45开始
u2 constant_pool_count 1 常量池计数值
cp_info constant_pool constant_pool_count - 1 常量池内容
u2 access_flags 1 类访问标识
u2 this_class 1 当前类
u2 super_class 1 父类
u2 interfaces_count 1 实现的接口数
u2 interfaces interfaces_count 实现接口信息
u2 fields_count 1 字段数量
field_info fields fields_count 包含的字段信息
u2 methods_count 1 方法数量
method_info methods methods_count 包含的方法信息
u2 attributes_count 1 属性数量
attribute_info attributes attributes_count 各种属性

示例

public class ClassFormat {
    private int m;

    public int inc() {
        return m + 1;
    }
}

直接查看Class文件:

image.png

我们可以使用javap -verbose ClassFormat直接翻译Class文件,而不用人工翻译,如:

PS E:\> javap -verbose ClassFormat
警告: 二进制文件ClassFormat包含com.xiaolyuh.ClassFormat
Classfile /E:/ClassFormat.class
  Last modified 2020-1-18; size 385 bytes
  MD5 checksum 6fe0cc9a0e7bf5b27718cb7e42ff6bfd
  Compiled from "ClassFormat.java"
public class com.xiaolyuh.ClassFormat
  minor version: 0                        // 次版本号
  major version: 52                       // 主版本号
  flags: ACC_PUBLIC, ACC_SUPER            // 类的访问标志
Constant pool:                            // 常量池
   #1 = Methodref          #4.#18         // java/lang/Object."":()V [从1开始]
   #2 = Fieldref           #3.#19         // com/xiaolyuh/ClassFormat.m:I
   #3 = Class              #20            // com/xiaolyuh/ClassFormat  [类索引(this_class)表示引用20索引位]
   #4 = Class              #21            // java/lang/Object [父类索引(super_class)]
   #5 = Utf8               m              // 成员变量m的简单名称
   #6 = Utf8               I              // 表示int类型
   #7 = Utf8                        // 实例构造器
   #8 = Utf8               ()V            // 没有参数,没有返回值的方法
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/xiaolyuh/ClassFormat;
  #14 = Utf8               inc             // 方法inc的简单名称
  #15 = Utf8               ()I
  #16 = Utf8               SourceFile
  #17 = Utf8               ClassFormat.java
  #18 = NameAndType        #7:#8          // "":()V
  #19 = NameAndType        #5:#6          // m:I
  #20 = Utf8               com/xiaolyuh/ClassFormat
  #21 = Utf8               java/lang/Object
{
  public com.xiaolyuh.ClassFormat();          // 构造函数
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1          // args_size=1的原因是this变量
         0: aload_0                           // 将局部变量槽 0(即this指针)的元素入栈
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return                            // 方法正常返回
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/xiaolyuh/ClassFormat;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/xiaolyuh/ClassFormat;
}
SourceFile: "ClassFormat.java"

更多指令请参考深入理解JVM - 虚拟机字节码指令集。

常量池

常量池中的第一个常量有特殊含义,所以常量池计数是从1开是的,其他计数是从0开始,如:接口索引集合。如上图,常量池计数是16,表示有22个常量,排除第一个,表示有21个常量,索引值范围为1~21。

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。

  • 字面量:比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
  • 符号引用:属于编译原理方面的概念,主要包括:类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。

访问标志

用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。

image.png

类索引、父类索引与接口索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。

字段表集合

字段表(field_info)用于描述接口或者类中声明的变量。Java语言中的“字段”(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。

字段可以包括的修饰符有字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。

字段访问标志:

image.png

描述符

描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示。对于数组类型,每一维度将使用一个前置的“[”字符来描述,如一个定义为“java.lang.String[][]”类型的二维数组将被记录成“[[Ljava/lang/String;”,一个整型数组“int[]”将被记录成“[I”。

描述符表:

java类型 类型描述符
boolean Z
char C
byte B
short S
int I
long J
float F
double D
Object Ljava/lang/Object;
int[] [I
Object[][] [[Ljava/lang/Object;

方法描述符:

方法 方法描述符
void m(int i, float f) (IF)V
int m(Object o) (Ljava/lang/Object;)I
int[] m(int i, String s) (ILjava/lang/String;)I
Object m(int[] i) ([I)Ljava/lang/Object;

方法表集合

描述了方法的定义,但是方法里的Java代码,经过编译器编译成字节码指令后,存放在属性表集合中的方法属性表集合中一个名为“Code”的属性里面。
与字段表集合相类似的,如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的便是类构造器“<clinit>”方法和实例构造器“<init>”。

属性表集合

存储Class文件、字段表、方法表都自己的属性表集合,以用于描述某些场景专有的信息。如方法的代码就存储在Code属性表中。

Code属性表的结构:

image.png
  • max_stack代表了操作数栈(Operand Stack)深度的最大值。
  • max_locals代表了局部变量表所需的存储空间。max_locals的单位是变量槽(Slot),变量槽是虚拟机为局部变量分配内存所使用的最小单位。对于byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个变量槽,而double和long这两种64位的数据类型则需要两个变量槽来存放。
  • code_length和code用来存储Java源程序编译后生成的字节码指令。

参考

《深入理解JAVA虚拟机》

你可能感兴趣的:(深入理解JVM - Class类文件的结构)