Class文件
1. Class文件内容解析
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有任何分隔符,采用大端(Big-Endian)排序方案,即高位字节在地址最低位。
数据项目的结构与C语言的结构体类似。这种数据结构只有两种数据类型:无符号数和表,无符号数用u1、u2、u4、u8来分别表示1个字节、2个字节、4个字节、8个字节长度的无符号数,用来描述数字、索引应用、数量值或按照UTF-8编码构成字符串值。表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表习惯性地以“_info”结尾,整个Class文件实质上等同于一张表。
魔数(magic):每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件,值为OxCAFEBABE。
Class文件的版本(minor_version/major_version):第5,6字节是次版本号(Minor Version),第7,8字节是主版本号(Major Version),0x0033(十进制为51)表示JDK7,0x0032(十进制为50)表示JDK6。
比如:0xCAFEBABE00000033,表示的是能被虚拟机接受的,次版本号为0x0000,主版本号为0x0033(JDK7)的Class文件的文件头。
常量池(constant_pool):可理解为Class文件中的资源仓库,所占空间最大的项目之一。常量池有两类常量:字面量(Literal)和符号常量 (Symbolic References),字面量接近Java语言层面的常量概念,如:文本字符串、声明为final的常量值,符号引用属于编译原理的概念,包括三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
共有14种项目类型:
名词解释————全限定名、简单名称、描述符
全限定名(Fully Qualified Name):某个类的全名为pers.kanarien.study.TestClass,则全限定名为pers/kanarien/study/TestClass,多个全限定名使用”;”间隔
简单名称:指没有类型或参数修饰的方法或字段名称,如方法test()和字段int a的简单名称分别为test和a
描述符(Descriptor):用来描述字段的数据类型、方法的参数列表(包括数量、类型、顺序)和返回值
访问标志(access_flags):表示一个类或接口层次的访问信息,包括这个Class文件是类还是接口;是否定义为public类型;是否定义为abstract类型等
类索引(this_class)、父类索引(super_class)、接口索引集合(interfaces):Class文件由以上三项确定继承关系。类索引用于确定这个类的全限定名;父类索引用于确定父类的全限定名;接口索引集合描述这个类实现了哪些接口。
字段表集合(field_info):用于描述接口或类中声明的变量。字段包括类变量和实例级变量,不包括方法内的局部变量。字段包含的信息有:字段的作用域(public、private、protected修饰符)、实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。字段表结构如下:
字段表集合不会列出从超类或父接口中继承而来的字段,但可能列出Java代码中不存在的字段,比如内部类为了保持对外部类党的访问性,自动添加指向外部类实例的字段。另外,Java语言中字段是无法重载的,即两个字段的名称必须不一样,但对字节码来说,两个字段的描述符不一致,字段重名是合法的。
方法表集合(method_info):与字段表集合类似,而方法中的Java代码存放在方法属性表一个名为“Code”的属性里面
属性表集合(attribute_info):Class文件、字段表、方法表都可以拥有自己的属性表集合,属性表不要求有严格的顺序。对于每个属性,它的名称都是从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构是完全自定义的,只需一个u4的长度属性去说明属性值所占的位数即可。
Java虚拟机规范中定义的属性:
- Code属性:Java程序方法体中的代码经过Javac编译器处理后,最终变成字节码指令存储在Code属性内。 Code属性出现在方法表的属性表集合之中,但并非所有方法都有Code属性,比如接口和抽象类中的方法。一个实例方法,至少有一个参数和一个本地变量,原因是
this
关键字。任何实例方法内,都可以通过this
关键字访问到此方法所属的对象,而Javac编译器编译的是否隐式的对this关键字的访问转变为对一个普通方法参数的访问,然后虚拟机在调用实例方法时自动传入此参数而已。Code属性有一个显式异常处理表(exception_info)如表6-16,用来实现Java异常和finally处理机制。 - Exceptions属性:列举方法中可能抛出的受查异常,也就是方法描述时在
throws
关键字后面列举的异常。 - LineNumberTable属性:用于描述Java源码行号与字节码行号(字节码偏移量)之间的对应关系。默认会生成到Class文件之中。如果不生成该属性,程序抛出异常时,堆栈不会显示行号,也无法设置断点调试程序。
2. 字节码指令
Java虚拟机的指令由一个字节长度的操作码,及其后若干的操作数组成。由于Java虚拟机采用面向操作数栈而不是寄存器的架构,因此大多数指令只有一个操作码,没有操作数。不考虑异常处理,Java虚拟机的最基本的执行模式如下伪代码所示1
2
3
4
5
6do {
自动计算PC寄存器的值加1;
根据PC寄存器指出的位置,从字节码流中取出操作码;
if (字节码存在操作数) 从字节码流中读出操作数;
执行操作码定义的操作;
} while (字节码流长度 > 0)
Java虚拟机指令集中,大多数的指令的本身包含了其操作所对应的数据类型信息,如iload指令用于从局部变量表中加载int型数据到操作数栈,而fload用于float类型的数据。由于操作码长度只有1个字节(256种情况),所以并非每一种数据类型和每一种操作都有对应的指令(Not Orthogonal) 。大部分指令都没有支持byte、char、short、boolean,编译器会在编译期或运行期将byte、char、short、boolean扩展为int型数据,其数组转换为int型数组,即byte、char、short、boolean类型的数据操作,实际上是使用相应的int型作为运算类型。
Copyright © 2018, GDUT CSCW back-end Kanarien, All Rights Reserved