编译的结果
在操作系统课上我们都学过,程序会被编译器翻译为由0和1构建的二进制格式,但是随着虚拟机以及大量建立在虚拟机上的程序语言的出现,将编写的程序编译成为二进制本地机器码已经不是唯一的选择了,越来越多的语言选择了与操作系统和机器指令集无关的、平台中立的格式作为程序编译后的存储格式。
Class类文件的结构
任何一个Class文件都对应着唯一的一个类或者接口的定义信息,但是,类或者接口并不一定都得定义在文件中,它们也可以动态生成,直接送入类加载器中。
Class文件是一组以8字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件中。根据《Java虚拟机规范》的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:“无符号数”和“表”:
- 无符号数属于基本的数据类型,以
u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个 字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。 - 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以
“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上也可以视作是一张表。
无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的“集合”。
魔数和Class文件的版本
魔数(Magic Number)是指在计算机编程或文件格式中,用于标识特定文件类型或数据结构的固定数值或字节序列。它通常位于文件的开头,帮助系统和程序快速识别文件格式。
每个Class文件的头4个字节(0xCAFEBABE)被称为魔数,它的作用是确定这个文件是否为一个能够被虚拟机接收的Class文件。
在魔数之后的四个字节是Class的版本号:第5和6字节是次版本号,第7和8字节是主版本号。
下面给一个示例:
public class TestClassFile {
private int m;
public int inc(){
return m++;
}
public static void main(String[] args) {
}
}
这段代码的字节码如下:
常量池
在主次版本号之后的就是常量池入口,常量池可以比喻为Class文件里的资源仓库,它是
Class文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一,另外,它还是在Class文件中第一个出现的表类型数据项目。
常量池中的常量数量不固定,因此在常量池入口放置一项u2类型数据,代表常量池容量计数器,比如下图中的1A代表有26个常量。
常量池中有两大类常量:字面量和符号引用。
Java代码在进行Javac编译的时候,并不像C/C++那样有连接这一步,而是在虚拟机加载Class文件的时候进行动态连接,因此在Class文件中不会保存各个方法、字段在内存中的布局信息,而是以字符串的形式引用比如以下这些:
- 类/接口:CONSTANT_Class_info -> "java/lang/Object"
- 字段: CONSTANT_Fieldref_info -> "java/lang/System.out:Ljava/io/PrintStream;"
- 方法: CONSTANT_Methodref_info -> "java/io/PrintStream.println:(Ljava/lang/String;)V"