java class文件布局架构

c语言将.c文件编译生成与硬体相关的二进制机器指令,问题在于不同的系统中的机器指令不同,所以c语言所编写的程序可移植性差。而java号称一次编写,到处运行(Write Once,Run Anywhere),java语言将.java文件经过编译成固定格式的.class文件,并在JVM上执行来达到这种平台无关的效果。也就是说:JVM不与任何包括java在内的语言做绑定,JVM只与固定格式的二进制class文件关联。这也正是为什么Clojure、Groovy、JRuby、Scala这么多新性语言都是在JVM上运行的原因,因为他们只要最后编译生成class文件即可。那么.class文件具体结构是怎么样的呢?

Class文件格式

数据类型

class是一个二进制文件,其中的数据都用无符号数表示,基本数据类型有4种:u1,u2,u3,u4。分别代表1、2、4、8四种字节的无符号数,这些无符号的数字用来表示各种字面量、符号引用、数值量或者utf-8构成的字符串值。除了上述四种基本数据类型还有一种复合数据类型:表。我们用_info结尾表示表结构,表是多个基本数据类型组合而成,用来表示多个层次上并列的关系的数据。实际上整个class文件也可以理解为一张表。

一个class文件的格式

类型 名称(CN) 名称 数量
u4 魔数 magic number 1
u2 次版本号 minor_version 1
u2 主版本号 major_version 1
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

因为class文件是二进制文件,其中没有任何的多余修饰标记,所以任何一处错误都会使得整个文件发生错位,上述文件格式被严格的规定不允许任何的变动。

魔数 magic number

class文件使用魔数来标识它可以被java虚拟机识别。事实上魔数不是java发明的,很多格式的文件都会用魔数来标识文件类型,因为相比较容易被用户修改的用户名,魔数是编辑在二进制文件内部的难以修改。在class文件中采用的魔数是0xCAFEBABY。

版本号 version

在魔数后的四个字节是版本号,五六个字节是次版本号,七八个字节是主版本号。这四个字节合起来准确的表达了当前class所需的最低jdk版本。版本号和jdk的版本之间的关系如下(主要几个版本):

jdk版本 class文件中版本号
JDK 1.18 45.3
JDK 1.5.0_11 49.0
JDK 1.6.0_01 50.0
JDK 1.7.0 51.0

上面简单的列出了几个对应关系,并不全面但是我们需要知道的是,版本号主要大概在45.3-(50+)左右。

常量池 constant_pool

因为常量的数量是不固定的,所以在class文件中常量池首先由一个constant_pool_count来表示所有常量的数量。其后跟随一个共有constant_pool_count-1个数据信息的表。比如常量数量为8,则说明有7个常量(索引为1~7),之所以没有0索引是因为我们把0索引空出来表示“不引用任何常量池”的含义。这也反映出常量池计数constant_pool_count最小值为1,表示不引用任何常量池。

我们将字面量符号引用两大类信息放入常量池中,作为常量信息存储起来。字面量表示文本字符串、声明为final类型的常量值。而符号引用则属于编译原理方面的概念,符号引用通常包括三大方面:

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

java在编译的过程中并没有c和c++的“连接”步骤,每个类之间并不能互通有无。java的处理方法是,在虚拟机加载class文件时将每个类有关类名、类方法名、字段名等信息的符号引用存放在方法区的常量池中,等到类被创建或者使用时解析将符号引用替换为直接引用。或许这么多的名词让你迷茫,总之简单的说,就是我们先用一个叫做符号引用的东西占位,当我们真正需要使用该类时在用正主把这个占位的替换掉,而这些所有的引用方式都存在于方法区的常量池中。

下面列举了14种常用的常量池的项目类型,包括字面量和引用两种:

这里写图片描述

常量池中14种常量项结构总表:
这里写图片描述

这里写图片描述

访问标志 access_flags

访问标志很简单,是用来表示此类的一些修饰符信息的。在常量池结束后,紧跟的两个字节信息就是访问标志,访问标志会唯一的标识出此类是否为public、是否定义为abstract、是否为final等信息。

类索引 this_class

类索引是一个u2类型数据,用来确定此类的全限定名。类索引通过指向一个类型为Constant_Class_Info来表述类的全限定名。

父类索引 super_class

父类索引也是一个u2类型数据,用来确定此类的父类的全限定名。父类索引通过指向一个类型为Constant_Class_Info来表述此类的父类的全限定名,如果此类是Object类,则没有父类,该u2数据为0。

接口索引集合 interfaces

接口索引集合由一个接口索引数量interfaces_count来做计数。后面的interfaces_count个u2数据表示接口的索引全限定名。