虚拟机可以运行在不同的平台上,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode)——各种不同平台虚拟机与所有平台都统一使用的程序存储格式,实现“一次编写,处处运行”。
虚拟机具有语言无关性。不和任何语言绑定,只与“class文件”这种特定的二进制文件格式所关联,class文件中包含了Java虚拟机指令集和符号以及若干其他辅助信息。
程序源文件-->对应的编译器-->.class字节码文件-->Java虚拟机
Class文件的结构
任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来,类或接口的信息并不一定都得定义在文件中,譬如类或接口可以通过类加载器直接生成。
Class文件,一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中,中间没有添加任何分隔符。class整个文件几乎全部是程序运行的必要数据,无空隙存在。Class文件采用类C结构体的伪结构来存储数据,只有两种数据类型:无符号数和表。
无符号数,基本数据类型,u1,u2,u4,u8(1/2/4/8个字节),可用来描述数字、索引引用,数量值或按照UTF-8编码构成的字符串。
表,多个无符号数或其他表作为数据项构成的负荷数据类型。所有表都习惯性以_info结尾。
如下表所示,由于没有任何分隔符,因此数据项的长度顺序数量字节序等都是被严格限定的,都不允许改变。
魔数与Class文件版本
每个Class文件头4个字节称为魔数(Magic Number),唯一作用是确定这个文件是否为一个能被虚拟机接受的class文件。
文件,图片等文件格式中都以魔数来识别,因为文件扩展名可以改变,而魔数只要不被混淆使用就可以。Java的魔数是0xCAFEBABE(咖啡宝贝)。
Java的版本号是从45开始的,JDK1.1以后每一个大半本发布主版本号向上+1,即7-8个字节(第三行,第二行是次版本号)。高版本能向下兼容,但不能运行以后的版本的class文件。
常量池
常量池,class文件的资源仓库,是与其他项目关联最多的数据类型,也是占用class文件空间最大的数据项目之一。同时还是class文件中第一个出现的表类型数据项目。
常量池数量是不固定的。因此需要在常量池入口放两字节来计数,且从1开始(只有常量池是从1开始的)。常量池容量(偏移地址0x00000008=十六进制0x0016,即十进制22,代表常量池中有21个常量,索引值为1-21)。0索引值是为了特定情况下不引用任何一个常量池项目时来设置。
常量池存放常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量,如文本字符串、声明为final的常量值等。符号引用包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
常量池中每一个向量都是一个表。常量池项目类型共14种。共同特点是表的第一位是一个u1类型的标志位(tag,如0x07,偏移地址0x00000007),代表其属于哪一种常量类型。14种常量类型各有其自己的结构。
class方法字段等都得引用CONSTANT_UTF9_info来描述名称,因此它的最大长度即Java方法、字段名的最大长度。例如length=u2,即最大值为65535,所以定义超过64KB就无法编译。
访问标志Access_flags
常量池之后两字节为访问标志,用于识别类或接口的层次访问信息,包括class是类还是接口,是否定义为public类型,是否定义为abstract类型,是类的话是否申明final等。共16个标志位,当前只定义了8个,没有定义的为0。
类索引、父类索引与接口索引集合
类索引(this_class),父类索引(super_class),接口索引集合(interfaces),都是u2类型(接口是一组u2的集合)。Class文件由这三项来确定这个类的继承关系。
类索引,即本类索引。单继承,因此父类索引只有一个。除了Object类以外,所有Java类的父类索引均不为0.接口索引集合表示这个类实现了哪些接口。被实现的接口(若本身为接口,则为继承)按implements的顺序从左到右排列在集合中。
父类和类索引都指向CONSTANT_Class_info,而接口索引的第一项为u2的接口计数器,若没有的话,为0,后面的索引表不占用任何字节。
字段表集合field_info
字段表,用于描述接口或类中声明的变量。字段包括类级变量以及实例级变量,但不包括方法内部声明的局部变量。
如:字段的作用域:public等;实例变量还是类变量(static修饰符),可变性(final),并发可见性(volatile修饰符,是否强制从主内存读写),可否被序列化(transient),字段数据类型(基本数据类型,对象,数组),字段名称。
其中,修饰符等都是布尔值,适合用标志位表示,而字段等类型无法固定,只能引用常量池中的常量来描述。
描述符的左右是描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。基本数据类型以及void都可以用一个大写字符来表示,如void->V,byte->B。数组[,二维数组[[,int[]->“[I”,String[][]->“[[Ljava/lang/String;”。L是指Ljava/lang/Object对象类型。先参数后返回值的顺序描述。
方法表集合
对方法和对字段的描述几乎完全一致。分为访问标志,名称索引,描述符索引,属性表计数器,属性表集合。
volatile关键字和transient关键字不能修饰方法,而synchronized,native,strictfp,abstract可修饰方法。
方法中的代码经过编译器编译成了字节码指令后,存放在方法属性表集合中的一个名为Code的属性里面,属性作为class文件格式中最具扩展性的一种数据项目。
编译器添加的实例构造方法<init>和源码中的方法gc();
如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类 的信息。但有可能出现由编译器自动添加的方法,最典型的便是类构造器<clinit>和实例方法构造器<init>。在Java中重载的话,除了方法名相同,还需要一个不同的特征签名,即方法中各个参数在常量池中的字符串引用的集合。但在class文件中,特征签名范围更大,可有相同名称和特征签名,但返回值不同的话,是可以合法共存同一个class文件中的。
属性表集合
字段表方法表都可以携带自己的属性表集合,以便于描述某些场景专有的信息。
属性表只要不重复属性名即可,无严格的顺序要求。
1.code属性——方法中的代码变为字节码指令后存储在code属性内。但接口或抽象类中的方法就不存在code属性。
在任何实例方法里面,都可以通过this访问到此方法所属的对象。实现非常简单。通过javac编译器编译时把this关键字的访问变为对一个普通方法参数的访问,然后在虚拟机调用实例时自动传入此参数。
异常表实际上是Java的一部分。编译器使用异常表而不是简单的跳转命令来实现Java异常及finally处理机制。
2.Exception属性——列出Code中可能抛出的受查异常,也就是throws后面列举的异常。
3.LineNumberTable属性——行号与字节码行号(字节码偏移量)之间的对应关系。运行时非必需。会默认生成到Class文件中。
4.LocalVariableTable属性——描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,也是运行时非必需的。会默认生成到class文件中。
5.sourceFile属性——用于记录生成这个class文件的源码文件名称。可选的。
6.ConstantValue属性——通知虚拟机自动为静态变量赋值。只有被static修饰的变量才可以使用该属性。
非Static,实例构造器中<init>; Static:ConstantValue或者<clinit>。
字节码指令简介
Java虚拟机的指令由一个字节长度的(0-255),有代表某种特定操作含义的数字(操作码Opcode)以及跟随其后的零至多个代表此操作所需参数操作数(Oprands)而构成。Java虚拟机采用面向操作数栈的架构,所以大多数指令都不包括操作数,只有一个操作码。用一个字节来代表操作码,也是为了尽可能获得短小精干的编译代码。
1.字节码和数据类型
iload->加载int型到操作数栈,fload->加载float到操作数栈。在class文件中必须拥有各自独立的操作码。
2.加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,包括:
将一个局部变量加载到操作栈,如iload,iload_<n>等。
将一个数值从操作数栈存储到局部变量表,istore,istore_<n>等;
将一个常量加载到操作数栈:bipush,sipush等;
扩充局部变量表的访问索引的指令:wide。
3.运算指令
加: idd,ladd等,减isub,lsub等.....
4.类型转换指令 int ->long.. long->float.. float->double...
5.对象创建与访问指令 new newarray getfield ....
6.操作数栈管理指令 pop pop2...
7.控制转移指令 ifeq ifle goto
8.方法调用和返回指令 invokevirtual...
9.异常处理指令
10.同步指令 由Monitor来支持的。
公有设计和私有实现
公有设计:共同的存储格式,class文件以及字节码指令集。
私有实现:必须能够读取class文件并精确实现包含在其中的Java虚拟机代码的语义。
实现方式: 将输入Java虚拟机代码在加载或执行时翻译成另外一种虚拟机指令集;或翻译成宿主机CPU的本地指令集,即JIT代码生成技术。
Class文件结构的发展
访问标志中添加一些新的标志。