深入理解Java虚拟机之.class文件的数据结构一

Class类文件的结构

无关性的基石

深入理解Java虚拟机之.class文件的数据结构一

上图是Java虚拟机实现语言无关性的生动描述。可以看出,Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件“这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。基于安全性方面的考虑,Java虚拟机规范要求在Class文件中使用许多强制性的语法和结构化约束,但任一门功能性语言都可以表示为一个能被Java虚拟机所接受的有效的Class文件。作为一个通用的,机器无关性的执行平台,任何其他语言都可以将Java虚拟机作为语言的产品交付媒介。

Class类文件结构

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储地内容几乎全部都是程序运行地必要数据,没有空隙存在。当遇到需要占用8位字节以上空间地数据项时,则会按照高位在前地方式分割成若干个8位字节进行存储。
根据Java虚拟机规范,Class文件格式采用一种类似于C语言结构体地伪结构体来存储数据,这种伪结构体中只有两种数据类型:无符号数和表

  • 无符号数

无符号数属于基本数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。

表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以"_info”结尾。表用于描述有层次关系地复合结构地数据,整个Class文件本质上就是一张表。
深入理解Java虚拟机之.class文件的数据结构一
上图是Class文件的文件格式,无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时称这一系列连续的某一类型的数据为某一类型的集合。

下面我们用一段Java代码作为实例来认识Class文件

深入理解Java虚拟机之.class文件的数据结构一

下图是使用WinHex打开Class文件的结果。

深入理解Java虚拟机之.class文件的数据结构一

1.魔数与Class文件的版本

深入理解Java虚拟机之.class文件的数据结构一

Class文件的前4个字节称为魔数(Magic Number),它是用来确定这个文件是否为一个Java虚拟机可以接受的Class文件;紧接着魔数的四个字节是Class文件的版本号(Major Version),第5个和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)(有关Java版本号的问题我们先略过),版本号是用来区别当前的Class文件是否可以被执行,通常的版本是向下兼容的。

2.常量池

深入理解Java虚拟机之.class文件的数据结构一

紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。

由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(Constant_pool_count),与java语言不同的是,容量计数器是从1开始计数的,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同。

常量池放了什么?

常量池中主要存放两大类常量,字面量(Literal)和符号引用(Symbolic References)。字面量比较接近Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符这三类常量。

Java代码在进行编译的时候是进行动态链接的。在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用需要从常量池获得对应的符号引用,再在类创建时或运行时解析,翻译到具体的内存地址之中。

常量池中的每一项常量都是一张表,在JDK1.7中总计有14种表结构:

深入理解Java虚拟机之.class文件的数据结构一

之所以说常量池是最烦琐的数据,是因为这14种常量类型各自均有自己的结构。有关其中的类型请笔者查看书中第169页的分析以及172页的总表结构。

深入理解Java虚拟机之.class文件的数据结构一

从图中我们可以看出计算机已经帮我们把整个常量池中18项常量计算了出来。