目录
越来越多的程序语言选择了与操作系统和机器指令集无关的、平台中立的格式作为程序编译后的存储格式。
Java的一大特点:一次编写,到处运行
各种不同平台的Java虚拟机,以及所有平台都统一支持的程序存储格式——字节码(Byte Code)是构成平台无关性的基石。
Java虚拟机提供的语言无关性:
class文件组成
在class文件中不仅存放了字节码,还存放了很多辅助JVM来执行class的附加信息。
1)结构信息:包括class文件格式版本号及各部分的数量与大小的信息
2)元数据:对应于Java源码中声明与常量的信息。包含类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池
3)方法信息:对应Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息
Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8个字节进行存储。
Class文件格式: 采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:“无符号数”和“表”。
1)无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
2)表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”
结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上也可以视作是一张表。
Class这个大表的文件格式:
无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的“集合”。
1. 魔数与Class文件的版本
1)魔数: 每个Class文件的头4个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。(使用魔数而不是扩展名来进行识别主要是基于安全考虑,因为文件扩展名可以随意改动)
Class文件的魔数取得很有“浪漫气息”,值为0xCAFEBABE
2)Class文件的版本号:
紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号(MinorVersion),第7和第8个字节是主版本号(Major Version)。Java的版本号是从45开始的,高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。
目前最新的JDK版本为13,可生成的Class文件主版本号最大值为57.0。
一个例子:
从JDK 1.2以后,直到JDK 12之前次版本号均未使用,全部固定为零。而到了JDK 12时期,由于JDK提供的功能集已经非常庞大,有一些复杂的新特性需要以“公测”的形式放出,所以设计者重新启用了副版本号,将它用于标识“技术预览版”功能“特性的支持。如果Class文件中使用了该版本JDK尚未列入正式特性清单中的预览功能,则必须把次版本号标识为65535,以便Java虚拟机在加载类文件时能够区分出来。
2. 常量池
紧接着主、次版本号之后的是常量池入口,常量池可以比喻为Class文件里的资源仓库,它是Class文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一,另外,它还是在Class文件中第一个出现的表类型数据项目。
在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
1)字面量:字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
①八种基本类型的值,eg: 1、1.0、true、'a'
②文本字符串,eg: "hello world"
③被声明为final的常量
2)符号引用:符号引用则属于编译原理方面的概念,主要包括下面几类常量:
·被模块导出或者开放的包(Package)
·类和接口的全限定名(Fully Qualified Name)
·字段的名称和描述符(Descriptor)
·方法的名称和描述符
·方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
·动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
Java代码在虚拟机加载Class文件的时候才进行动态连接。也就是说,在Class文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
常量池中每一项常量都是一个表,截至JDK13,常量表中分别有17种不同类型的常量。
这17类表都有一个共同的特点,表结构起始的第一位是个u1类型的标志位(tag,取值见表6-3中标志列),代表着当前常量属于哪种常量类型。
示例:
package basic;
public class ConstantsTest {
public String name = "Hello World";
public final int num = 100;
public ConstantsTest(String name) {
this.name = name;
}
public void info() {
System.out.println(name);
System.out.println(num);
}
}
字面量
-
字符串:
“Hello World”
-
被final修饰的基本类型值:
100
符号引用
-
类和接口的全限定名:
basic/ConstantsTest
、Object
-
字段的名称和描述符:
basic/ConstantsTest.name:Ljava/lang/String;
、basic/ConstantsTest.num:I
-
方法名称和描述符:
java/lang/Object."<init>":()V
(构造方法)、info
、java/io/PrintStream.println:(Ljava/lang/String;)V
(第一个print)、java/io/PrintStream.println:(I)V
(第二个print)等
字符串常量池示例:
String str1 = “abc”;
String str2 = “abc”;
String str3 = “abc”;
String str4 = new String(“abc”);
String str5 = new String(“abc”);
//栈中存放对象引用,堆中创建对象
3. 访问标志
在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final;