概述
Sun公司以及其他虚拟机提供商发布了许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现“一词编写,到处运行”。这些虚拟机和平台统一使用的程序存储格式就是字节码(ByteCode)。
代码编译的结果从本地机器码转变为字节码。
实现语言无关性的基础是虚拟机和字节码存储格式,Java虚拟机不和任何语言绑定,只和“Class文件”这种特定的二进制文件格式所关联。作为一个通用的,与机器无关的执行平台,其他语言都可以将Java虚拟机作为语言的产品交付媒介。
Class类文件结构
任何一个Class文件都对应唯一一个类或接口的定义信息,但类和接口并不一定都定义在文件里(比如类或接口也可以通过类加载器直接生成)。Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑排列,没有任何分隔符。当遇到需要占用8位字节以上空间的数据项时,则按照高位在前的方式分割成若干8位字节进行存储。
Class文件格式类似于C语言结构体的伪结构,包括无符号数和表。
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
u2 fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有个字段
u2 methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
}
-
魔数
确定这个文件是否时一个能被虚拟机接受的Class文件,例如图片格式也用魔数进行身份识别。 -
Class文件版本
第五和第六个字节是次版本号(Minor Version),第七和第八个字节是主版本号(Major Version)。高版本JDK能向下兼容以前版本的Class文件,但不能运行以后版本的。 -
常量池
Class文件中的资源仓库。常量池入口需要放置一个u2类型的数据,代表常量池容量计数值,从1开始(只有常量池容量技术从1开始)。
常量池存放两大常量:字面量和符号引用,字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:
– 类和接口的全限定名
– 字段的名称和描述符
– 方法的名称和描述符
常量池中每一项常量都是一个表,表开始第一位是u1类型的标志位,代表哪种常量类型。 -
访问标志
识别一些类或接口层次的访问信息
包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。 -
当前类索引、父类索引和接口索引集合
类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合。Class文件中由这三项数据确定这个类的继承关系。
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。
接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按implents(如果这个类本身是接口的话则是extends) 后的接口顺序从左到右排列在接口索引集合中。 -
字段表集合
用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
– access_flags: 字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。
– name_index: 对常量池的引用,表示的字段的名称;
– descriptor_index: 对常量池的引用,表示字段和方法的描述符;
– attributes_count: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;
– attributes[attributes_count]: 存放具体属性具体内容。
上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型这些都是无法固定的,只能引用常量池中常量来描述。
字段访问标志:
-
方法表集合
Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
方法里的代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里。 -
属性表集合
在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。
参考
- 《深入理解Java虚拟机》