平台无关性
“与平台无关”实现在操作系统的应用层上,通过实现可以运行在各种不同平台上的虚拟机,而这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现程序的“一次编写,到处运行”。
各种不同平台的虚拟机与所有平台都统一是使用的程序存储格式:字节码(ByteCode)是构成平台无关性的基石
语言无关性
现在有很多语言都可以运行在Java虚拟机之上,比如Clojure、Groovy、JRuby、Jython、Scala等等,从一定意义上,JVM实现了语言无关性
而实现语言无关性的基础仍然是JVM和字节码存储格式。Java虚拟机不合包括Java在内的任何语言绑定,之和“Class”文件这种特定的二进制文件格式关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其它辅助信息。JVM只关注有效的Class文件,而不关心CLass文件来源于何种语言,是由哪种语言编译而成。
Class文件结构
class文件头
4个字节的魔数(0XCAFEBABE)+ 2个字节的次版本号 + 2个字节的主板本号
常量池
在主次版本号后是常量池入口。
常量池中常量的数量不固定,故在常量池入口放置一个u2类型的数据,代表常量池容量计数值(constant_pool_count),这个容量技术从1开始。
常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
1. 字面量:如文本字符串、声明为final的常量值等
2. 符号引用:包括下列三类常量:
1. 类和接口的全限定名(Fully Qualified Name)
2. 字段的名称和描述符(Descriptor)
3. 方法的名称和描述符
访问标志
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),用于区别一些类或者接口层次的访问信息,如这个Class是类还是接口;是否public、abstract等
类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,class由这三项数据来确定这个类的继承关系,类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,接口集合索引用来描述这个类实现了哪些接口,根据implements语句顺序从左到右排列。
字段表集合
字段表(field_info)用于描述接口或者类中声明的变量。字段包括类级变量和实例级变量,但不包括方法内部声明的局部变量。字段表结构包括
1. 访问标志(access_flags):包括public、private、protected、static、final等
2. 名称索引(name_index):对常量池的引用,代表字段的简单名称
3. 描述符索引(descriptor_index):对常量池的引用,代表字段和方法的描述符,描述符用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值,如byte(B)\char(C)\double(D)\void(V)\对象类型(L)等
4. 属性表集合(attributes)
5. 属性表数量(attributes_count)
方法表集合
方发表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes),属性表数量(attributes_count)几项。
属性表集合
属性表用于描述某些场景的专有信息,常用的有:
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方发表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | finaluguanjianzi定义的常量值 |
Deprecated | 类、方法表、字段表 | 被生命为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号和字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
SourceFile | 类文件 | 源文件名称 |
Systhetic | 类、方法报、字段表 | 标识方法或字段为编译器自动生成的 |
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量表来表示,而属性值的结构则是完全自定义的,只要说明属性值所占用的位数长度即可。一个符合规则的属性表应该满足如下表定义的结构:
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u2 | attribute_length | 1 |
u1 | info | attribute_length |
Java字节码指令
加载和存储指令
用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
运算指令
用于对两个操作数栈上的值进行某种特定的运算,如加减乘除等,计算完把结果会重新存入到操作栈顶中。
类型转换指令
可以将两种不同的数值类型进行相互转换,一般用于实现用户代码中显式类型转换
对象创建和访问指令
Java虚拟机对类实例和数组的常见与操作使用了不同的字节码指令。对象创建后,可以通过对象访问指令获取对象实例或者数组实例中的字段或者数组元素,常用有new等。
操作数栈管理指令
用于直接操作操作数栈的指令
控制转移指令
使Java虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序。
方法调用和返回指令
用于处理调用对象的实例方法、接口方法等操作。
异常处理指令
用于显式抛出异常等操作
同步指令
JVM支持方法级的同步以及方法内部一段指令序列的同步,这两种同步结构都使用管程(Monitor)来支持