对于习虚拟机来说,Class文件是个绕不开的话题,但他内容比较复杂,尤其涉及数据结构的部分,很是难懂,本文仅对Class文件的大体结构进行总结
Class文件是以8位字节为基础单位的二进制流,其结构中只有两种数据类型,无符号和表;对于无符号,u1,u2,u4,u8分别代表一个字节,两个字节,四个字节和八个字节的无符号数,无符号数可以用来描述数字,引用类型,数量值和字符串;对于表,他是由无符号数或其他的表组成的复杂数据类型;整个Class文件实际上就是一张表;
下面对Class文件中的各部分结构进行介绍;
首先,文件最开始的四个字节称为魔数,它的唯一作用就是确定该文件是否能被java虚拟机接受,这个魔数的值是0xCAFEBABE,据说代表着一种很好喝的咖啡;
接下来的四个字节表示版本号,jdk是向下兼容的,反过来却不行;
版本号后是常量池,他是Class文件中与外界联系最多的数据类型,也是Class文件中占据空间最大的数据之一;常量池中常量的数量是不确定的,所以常量池入口有一个常量池计数器表示容量;常量池中主要有两类常量,即字面量与符号引用,字面量如文本字符串和声明为final的常量,符号引用包括类和接口的权限定名,字段和名称的描述符,方法名称和描述符;在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
常量池之后是两个字节的访问标识,这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等;
之后是类索引,父类索引,和接口索引集合,类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,一个类的父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中。
字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。描述的信息有:字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称;字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
下面是方法表,Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项;而方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面;
属性表:在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。如Code属性是方法表中的,ConstantValue属性是字段表中的等等;