代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。
Java 虚拟机使用字节码实现了跨平台的愿景,不仅针对Java语言,实现了write once,run anywhere的愿景;随着发展,越来越多其他语言可以在Java虚拟机之上运行,如Kotlin、Clojure、Groovy、JRbuy、JPython、Scala等。
Java虚拟机不与包括Java语言在内的任何程序语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集、符号表以及若干其他辅助信息。

Class文件是一组以字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在文件之中,中间没有任何分隔符。Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表。
无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1字节、2字节、4字节和8字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。
表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有的表习惯性以“_info”结尾。表用于描述有层次关系的复合结构数据,整个Class文件本质上可以视作一张表,这张表严格按照下表1-1所列顺序排列。
表1-1 Class 文件格式
为了方便讲解,准备了如下的Java代码。
相应的类文件结构如下图所示:
1.1 魔数与Class文件版本
每个Class文件的头四个字节被称为魔数,用以确定这个文件是否为一个能被虚拟机接受的Class文件,值为0x CA FE BA BE。第五六字节 0x 00 00代表次版本号。主版本号值为0x 00 32,即十进制的50,说明是JDK6或以上版本虚拟机执行的Class文件。
1.2 常量池
由表1-1可知,主次版本号后,是常量池入口,常量池被比喻为Class文件里面的资源仓库,是Class文件结构中与其他项目关联最多的数据。
首先是u2类型的数据代表常量池容量计数值(constant_pool_count),这个容量技术从1开始,如果不引用任何常量池项目,则把索引值设置为0。由下图0x 00 16,即十进制的22,代表常量池中有21项常量,索引范围为1~21。
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References),字面量比较接近于Java语言层面的常量概念,如文本字符串,被声明为final常量值等。而符号引用则属于编译原理方面的概念,主要包括以下几类常量:
被模块导出或者开放的包(Package)
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)
方法的名称和描述符
方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
常量池中的每一项常量都是一个表,常量表中有17种不同类型的常量,所有表结构起始的第一位是个u1类型的标志位,代表当前常量属于哪种常量类型。
图6-3常量池结构中,常量池容量0x 00 16后紧跟的u1标志位0x 07查表6-3可知,是一个CONSTANT_Class_info类型,此类型的常量代表一个类或者接口的符号引用。
name_index是常量池的索引值,它指向常量池中一个CONSTANT_Utf8_info类型常量,此常量代表了这个类的全限定名。本例中的name_index值为0x 00 02,指向了常量池中的第二项常量。第二项常量的标志位是0x 01,查表6-3可知,是CONSTANT_Uff8_info类型的常量。其结构表如表6-5所示。
length值说明了UTF-8字符串长度是多少个字节,后面紧跟长度为length字节的,UTF-8缩略编码表示的字符串。
UTF-8缩略编码与普通UTF-8编码的区别是:从'\u0001'到'\u007f'之间的字符(相当于1~127的ASCII码) 的缩略编码使用一个字节表示,从'\u0080'到'\u07ff'之间的所有字符的缩略编码用两个字节表示, 从'\u0800'开始到'\uffff'之间的所有字符的缩略编码就按照普通UTF-8编码规则使用三个字节表示。
本例中length值长度为0x 00 1D, 长度是29个字节,往后的29个字节都在1~127的ASCII码范围以内,内容为“org/fenixsoft/clazz/TestClass”
剩下的19个常量与上面讨论的类似,可以通过javap -verbose 类文件名查看字节码内容,如下图所示。
不难发现,常量池中存在“I”“V”“<init>”“LineNumberTable”“LocalVariableTable”等, 这些看起来在源代码中不存在的常量,它们是编译器自动生成的,会被字段表(field_info)、方法表(method_info)、属性表(attribute_info)所引用,用于表示方法的返回值,有几个参数,每个参数的类型等信息。
以下是常量池中的17种数据类型的结构总表
1.3 访问标志
由表6-1,常量池结束后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息。具体标志值及其含义见下表。
没有用到的标志位一律为0。由标志位为0x 00 21可知,ACC_PUBLIC 和 ACC_SUPER标志为真。