书中的第三部分主要来了解一下虚拟机执行子系统。这部分有四个部分:类文件结构、虚拟机类加载机制、虚拟机字节码执行引擎和类加载及执行子系统的案例与实战。
我们先来看一下类文件结构。
学习资料主要参考: 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》,作者:周志明
概述
Java刚刚诞生时,提出了一个非常著名的口号:“一次编写,到处运行(Write once, Run Anywhere)”。
这个口号的基础就是:不管是运行在什么平台上的虚拟机,都可以载入和执行同一种与平台无关的字节码。
当然这个字节码,不仅可以是Java语言的,也可以是其他语言。
Class类文件的结构
Class文件是一组8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,之间没有添加任何分隔符。
基本数据类型
存储结构中分为两种数据类型:无符号数和表。
-
无符号数
属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数。
它可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
-
表
由多个无符号数或者其他表作为数据项构成的复合数据类型。
所有表都习惯地以“_info”结尾。
表用于描述有层次关系的复合结构的数据。
1. 魔数与Class文件的版本
每个Class文件的头4个字节称为魔数。 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。 为:“0xCAFEBABE”
紧接着魔数的4个字节为Class文件的版本号:第5、6字节是次版本号,第7、8字节是主版本号。
2. 常量池
主次版本号后是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的的数据项目之一,同时它也是在Class文件中第一个出现的表类型数据项目。
常量池入口放置了一项u2类型的数据,来代表常量池容量计数值,这个计数值是从1开始的。所以常量数目是计数值减一。
常量池找那个存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量,如文本字符串、声明为final的常量值等。符号引用包括以上三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
可以借助javap命令来帮助我们理解Class文件。
3. 访问标志
常量池之后,紧接着的两个字节代表访问标志。
这个标志用于识别一些类或者接口层次的访问信息。比如:这个Class是类还是接口?是否定义为public类型?是否为abstract类型?是否是final 类?
4. 类索引、父类索引与接口索引集合
类索引、父类索引都是一个u2类型的数据。接口索引集合是一组u2的数据结合。
Class文件通过这三项数据来确定这个类的继承关系。
5. 字段表集合
字段表用于描述接口或者类中声明的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
包括的信息有:字段的作用域、实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile)、可否被序列化(transient修饰符)、字段数据类型、字段名称。字段类型和字段名称都是无法固定的,需要引用常量池中的常量来描述。其他的信息可以用标志位来表示。
6. 方法表集合
与字段表几乎一致,依次包括访问标志、名称索引、描述符索引、属性表集合。
7. 属性表集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
字节码指令简介
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(操作码)以及跟随其后的零至多个代表此操作所需参数(操作数)而构成。
- 字节码与数据类型
- 加载和存储指令
- 运算指令
- 类型转换指令
- 对象创建于访问指令
- 操作数栈管理指令
- 控制转移指令
- 方法调用和返回指令
- 异常处理指令
- 同步指令