# Class 文件格式(定义)

  • 任何一个 Class 文件都对应着唯一一个类或接口的定义信息(但并不一定以磁盘文件的形式存在。譬如通过类加载器直接生成类或接口)。
  • Class 文件是一组以 8 位字节为基础单位的二进制流其中各个数据项目严格按照顺序紧凑地排列(没有任何分隔符)。
  • 当遇到需要占用 8 位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个 8 位字节进行存储。
  • 使用伪结构存储数据:无符号数(以 u1、 u2、 u4、 u8 来分别代表 1 个字节、 2 个字节、 4 个字节和 8 个字节的无符号数)和表(由多个无符号数或者其他表作为数据项构成的复合数据类型所有表都习惯性地以 “_info” 结尾)。
  • 某一类型的集合:使用一个前置的容量计数器加若干个连续的(某一类型)数据项的形式。
  • 在 Class 文件中不会保存各个方法、 字段的最终内存布局信息,当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、 翻译到具体的内存地址之中。

# 特殊字符串的概念

  • 字段、方法的简单名称。例:“inc”-->inc ()
  • 类和接口的全限定名。例:“org/fenixsoft/clazz/TestClass;”
  • 描述符用来描述字段的数据类型、 方法的参数列表(包括数量、 类型以及顺序)和返回值。
    描述符标识符含义
  • 数组描述示例:
    java.lang.String[][] ---> [[Ljava/lang/String;
    int[] ---> [I
  • 方法描述示例:
    void inc() ---> ()V
    java.lang.String toString() ---> ()Ljava/lang/String;
    int indexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,int fromIndex) ---> ([CII[CIII)I

# Class 文件结构组成

# 魔数(u4)

  • 每个 Class 文件的头 4 个字节称为魔数(Magic Number),用来确定是否为一个能被虚拟机接受的 Class 文件。class 文件固定魔数值为:0xCAFEBABE

# Class 文件的版本(u2+u2)

  • 紧接着魔数的 4 个字节存储的是 Class 文件的版本号:第 5 和第 6 个字节是次版本号(Minor Version),第 7 和第 8 个字节是主版本号(Major Version)。

# 常量池

  • 紧接着主次版本号之后的是常量池入口,偏移地址:0x00000008 处 u2 类型数据代表常量池容量计数值(constant_pool_count)(索引从 1 开始)。
  • 常量池中主要存放两大类常量:字面量(Literal)(如文本字符串、 声明为 final 的常量值等。)和符号引用(Symbolic References)。
  • 符号引用包含三类常量:类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符
  • 常量池中每一项常量都是一个表,开始的第一位是一个 u1 类型的标志位(tag)。
  • JDK1.7 中常量池项目类型:
    JDK1.7中常量池项目类型

# 常量结构表

常量结构表

  • 查看 class 文件内常量表
  javap -verbose TestClass

# 访问标志(access_flags:u2 长度)

  • 用于识别一些类或者接口层次的访问信息
    访问标志

# 类索引(u2)、 父类索引亅(u2)与接口索引集合(u2 集合)

  • 类索引用于确定这个类的全限定名。指向一个类型为 CONSTANT_Class_info 的类描述符常量
  • 父类索引用于确定这个类的父类的全限定名(每个类只有一个),除了 java.lang.Object 之外所有类都有父类(父类索引都不为 0)。指向一个类型为 CONSTANT_Class_info 的类描述符常量
  • 类、父类索引查找全限定名过程
    类、父类索引查找全限定名过程
  • 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身是一个接口,则应当是 extends 语句)后的接口顺序从左到右排列在接口索引集合中。
  • 接口索引集合入口第一项为 u2 类型数据接口计数器(interfaces_count)

# 字段表集合入口(第一个 u2 型数据)为容量计数器(fields_count)

  • 表示字段表集合的长度

# 字段表集合

  • 字段表(field_info)用于描述接口或者类中声明的变量。
  • 字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
  • 字段表结构
    字段表结构
  • 字段访问标志(access_flags)
    字段访问标志
  • 字段的简单名称索引(name_index)
  • 字段和方法描述符(descriptor_index)
  • 属性表集合(attributes_count (u2)+attribute_info)
    字段都可以在属性表中描述零至多项的额外信息。

# 方法表集合入口(第一个 u2 型数据)为容量计数器(methods_count)

  • 表示方法表集合长度

# 方法表集合

  • 方法表结构
    方法表结构
  • 方法访问标志
    方法访问标志
  • 如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。
  • 有可能出现由编译器自动添加的方法,如:类构造器 “<clinit>” 方法和实例构造器 “<init>” 方法。

# 属性表集合

  • 用于描述某些场景专有的信息。
  • 在 Class 文件、 字段表、 方法表都可以携带自己的属性表集合。
  • 属性表结构
    属性表结构

# 几种虚拟机规范预定义属性说明

# Code 属性
  • 存放 Java 程序方法体中的代码经过 Javac 编译器处理后的字节码指令。
  • 接口或者抽象类中的方法就不存在 Code 属性。(没有方法体)
# code 属性表结构

code属性结构

  • code 属性表中 attribute_name_index 是一项指向 CONSTANT_Utf8_info 型常量(常量值固定为 Code)的索引 u2 类型数据
  • attribute_length 指示了属性值的长度。u4 类型数据
  • 属性值的长度固定为整个属性表长度减去 6 个字节 u2+u4
  • max_stack 代表了操作数栈(Operand Stacks)深度的最大值,虚拟机运行的时候需要根据这个值来分配栈帧(StackFrame)中的操作栈深度。
  • max_locals 代表了局部变量表所需的存储空间,max_locals 的单位是 Slot。
  • code_length 代表字节码长度(虚拟机规范中明确限制了一个方法不允许超过 65535 条字节码指令即(u2 类型长度))
  • code 是用于存储字节码指令的一系列字节流(每个指令就是一个 u1 类型的单字节)。
  • 显式异常处理表集合(当方法有使用异常处理机制时会有该属性)
  • 异常表结构
    显式异常表结构
  • 异常表解读:如果当字节码在第 start_pc 行到第 end_pc 行之间(不含第 end_pc 行)出现了类型为 catch_type 或者其子类的异常(catch_type 为指向一个 CONSTANT_Class_info 型常量的索引),则转到第 handler_pc 行继续处理。 当 catch_type 的值为 0 时,代表任意异常情况都需要转向到 handler_pc 处进行处理。
# 对 Slot 的说明
  • Slot 是虚拟机为局部变量分配内存所使用的最小单位。
  • 长度不超过 32 位的数据类型每个局部变量占用 1 个 Slot,反之需要两个 Slot 来存放。
  • 方法参数方法参数(包括实例方法中的隐藏参数 “this”)、 显式异常处理器的参数(Exception Handler Parameter,就是 try-catch 语句中 catch 块所定义的异常)、 方法体中定义的局部变量都需要使用局部变量表来存放。
  • Slot 存在复用的可能性
  • Javac 编译器会根据变量的作用域来分配 Slot 给各个变量使用,然后计算出 max_locals 的大小。
  • 在任何实例方法里面,都可以通过 “this” 关键字访问到此方法所属的对象。
  • 局部变量表中也会预留出第一个 Slot 位来存放对象实例的引用(this)。
# Exceptions 属性
  • 列举出方法中可能抛出的受查异常(Checked Excepitons)。即方法描述时在 throws 关键字后面列举的异常。
  • Exception 属性结构
    Exception属性结构
  • number_of_exceptions 项表示方法可能抛出 number_of_exceptions 种受查异常。
  • 每一种受查异常使用一个 exception_index_table 项表示,exception_index_table 是一个指向常量池中 CONSTANT_Class_info 型常量的索引,代表了该受查异常的类型。
# LineNumberTable 属性
  • 用于描述 Java 源码行号与字节码行号(字节码的偏移量)之间的对应关系。
  • 非必需,默认会生成到 Class 文件之中,可以在 Javac 中分别使用 - g:none 或 - g:lines 选项来取消或要求生成这项信息。
  • 如果不生成该属性当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。
  • 属性结构
    LineNumberTable属性
  • line_number_table 是一个数量为 line_number_table_length、 类型为 line_number_info 的集合,line_number_info 表包括了 start_pc 和 line_number 两个 u2 类型的数据项,前者是字节码行号,后者是 Java 源码行号。
# LocalVariableTable 属性
  • 用于描述栈帧中局部变量表中的变量与 Java 源码中定义的变量之间的关系。
  • 非必需,默认会生成到 Class 文件之中,可以在 Javac 中分别使用 - g:none 或 - g:vars 选项来取消或要求生成这项信息。
  • 如果不生成该属性当其他人引用这个方法时,所有的参数名称都将会丢失 IDE 将会使用诸如 arg0、arg1 之类的占位符代替原有的参数名
  • 属性结构
    LocalVariableTable属性
  • local_variable_info 项目代表了一个栈帧与源码中的局部变量的关联,其结构如下图:
    local_variable_info
  • local_variable_info 结构中参数说明:
  • start_pc 和 length 属性分别代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围。
  • name_index 和 descriptor_index 都是指向常量池中 CONSTANT_Utf8_info 型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符。
  • index 是这个局部变量在栈帧局部变量表中 Slot 的位置。 当这个变量数据类型是 64 位类型时(double 和 long),它占用的 Slot 为 index 和 index+1 两个。
# LocalVariableTypeTable 属性
  • 在 JDK 1.5 引入泛型之后,LocalVariableTable 属性无法准确描述泛型类型,因此使用 LocalVariableTypeTable 属性,把记录的字段描述符的 descriptor_index 替换成了字段的特征签名(Signature)。
# SourceFile 属性
  • SourceFile 属性用于记录生成这个 Class 文件的源码文件名称。
  • 非必需,可以分别使用 Javac 的 - g:none 或 - g:source 选项来关闭或要求生成这项信息。
  • 属性结构
    SourceFile属性
  • sourcefile_index 数据项是指向常量池中 CONSTANT_Utf8_info 型常量的索引,常量值是源码文件的文件名。
# ConstantValue 属性
  • 通知虚拟机自动为静态变量赋值。
  • 只有被 static 关键字修饰的变量(类变量)才可以使用这项属性。
  • 虚拟机对于非 static 类型的变量(也就是实例变量)的赋值是在实例构造器<init>方法中进行的。
  • ConstantValue 属性结构
    ConstantValue属性结构
# 对于 static 类型变量 sun Javac 编译器的做法:
  • 同时使用 final 和 static 来修饰一个变量(常量),并且数据类型是基本类型或者 java.lang.String,就生成 ConstantValue 属性来进行初始化
  • 如果这个变量没有被 final 修饰,或者并非基本类型及字符串,则将会选择在<clinit>方法中进行初始化。
# InnerClasses 属性
  • 用于记录内部类与宿主类之间的关联。
  • 如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成 InnerClasses 属性。
  • 属性结构图
    InnerClasses属性
# StackMapTable 属性
  • 会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用。
# Signature 属性
  • 用于记录类、 接口、 初始化方法或成员的泛型签名信息。
  • Java 语言采用擦除法实现的伪泛型
  • Java 的反射 API 使用该属性能够获取泛型类型
# BootstrapMethods 属性
  • 用于保存 invokedynamic 指令引用的引导方法限定符。