Class类文件的结构

Class类文件就是java文件通过编译之后产生的文件,了解Class类文件的结构对Java虚拟机的学习是不可或缺的。它有助于我们对许多JVM的知识更方便理解,例如类加载机制、对象的创建、方法区区域的内容等会有更好的体会。


注意:任意一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载起直接生成)


Class类文件的构成

  Class文件是一组以8位字节为基础单位二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。
  Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表,后面的解析都要以质量中数据类型为基础,所以这里要先介绍这两个概念。
  无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
  是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有的表都习惯性的以“_info”结尾。整个Class文件本质上就是一张表。

Class类文件的结构

下面简单介绍几个常用的数据项
  
  

魔数与Class文件版本

  魔术就是对应表中的magic数据项,每个Class文件的头4个字节称为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接收的Class文件。
  紧接着魔数的4个字节存储的是Class文件的版本号:第5个和第6个字节是次版本号(minor_version),第7第8个字节是主版本号(major_version)。
  
  

常量池

  紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一
  由于常量池中常量的数量是不固定的,所有在常量池的入口需要放置一项u2类型数据,代表常量池容量计数值。(它是从1开始计数的,比如常量池容量为十六进制数 0x0016,即十进制22,代表常量池中有21项常量,索引值范围为1~21)
  常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括下面三类常量:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法 的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

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

  类索引和父类索引都是一个u2类型的数据,而接口索引集合时一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。

Class类文件的结构
  
  

字段表集合

  字段表(field_info)用于描述接口或者类中声明的变量。字段包括类变量和实例变量,但不包括在方法内部声明的局部变量。
  那么一个成员变量对应的一个字段表包含什么信息呢?包含:字段的作用域(public、private、protected、default)、是否类变量、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本数据类型、对象、数组)、字段名称。上述这些信息,各个修饰符都是布尔值,但是字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述

Class类文件的结构

图中accecc_flags数据项用来表示字段修饰符
name_index用来表示字段的名称,这里引用常量池中的常量(字面量)
descriptor_index 表示字段的数据类型,这里引用常量池中的常量(符号引用),在根据符号引用确定变量数据类型。
attributes_count 表示一个属性表集合的容量
attributes 表示一个属性表集合
   
   

方法表集合

  方法表集合和字段表集合很相似。
Class类文件的结构  

这里的name_index表示方法名,这里引用常量池中的常量(字面量)
descriptor_index 表示方法返回的类型,这里引用常量池中的常量(符号引用),在根据符号引用确定变量数据类型。
attributes 存在一个属性Code,它是一个表,里面存放着关于这个方法更多详细的信息,例如操作数栈深度的最大值、局部变量表所需的存储空间等。这些都是在编译期就可以确定下来的。

—参考:深入理解Java虚拟机 第2版 ,周志明著