一、语言无关性与平台无关性
语言无关性:Java虚拟机上运行的是Class文件(字节码文件*.class),而Class文件不一定由Java程序编译而来,JRuby经过jrubyc编译器编译生成的是Class文件,Groovy程序经过groovyc编译器编译后生成的也是class文件,都可在虚拟机上运行,虚拟机不关心Class的来源是何种语言。
平台无关性:一次编写,到处运行,各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码。
二、Class类文件的结构
以字节为基础单位:
Class文件是一组以8字节为基础单位的二进制流,各数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
两种数据类型:无符号数和表
无符号数属于基本的数据类型,u1表示1个字节,u2表示2个字节,u4代表4个字节。
表是由多个无符号数或者其他表作为数据项构成的符合数据类型。
魔数:
Class文件的头4个字节称为魔数,确定这个文件是否为一个能被虚拟机接收的Class文件。值为0xCAFEBABE。
版本号:
紧接着4个字节存储的是Class文件的版本号。Minor Version(2字节)+Major Version(2字节)。
常量池:
紧接着版本号的是常量池入口,常量池可以理解为Class文件之中的资源仓库。
主要存放两大类常量:字面量和符号引用。
字面量:文本字符串、声明为final的常量值等。
符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述。
Java代码在进行编译时,不像C和C++那样有“连接”这一步骤,而是虚拟机加载Class文件的时候进行动态连接。
访问标志:
用于识别一些类或者接口层次的访问信息,包括Class是类还是接口,是否为public类型,是否abstract类型,是否为final类型。
类索引、父类索引接口与接口索引集合:
类索引:u2类型的数据,指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。
父类索引:u2类型,其他和上面一样。
接口索引集合:因为单个类可以实现多个接口,所以入口的第一项为u2类型的接口计数器。后面是接口集合。
字段表集合:
用于描述接口或者类中声明的变量。
字段的修饰符(有或无,布尔类型)用标志位来表示。如public、static、final等
字段的名字、字段被定义为什么数据类型,这些无法固定,只能引用常量池的常量来表示。
字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如内部类为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
方法表集合:
类似于字段表集合,包括访问标志、名称索引、描述符索引、属性表集合。
描述方法的定义,方法里面的代码存放在方法属性表集合中一个名为Code的属性里面。
其中<init>为编译器为添加的的实例构造器。
如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息。
要重载一个方法,除了要与原方法有相同的简单名称之外,要求有一个与原方法不同的特征签名。特征签名就是一个方法中各个参数在常量池中字段符号引用的集合,返回值不会包含在特征签名中,因此无法靠返回值的不同来对一个已有方法进行重载。
属性表集合:
Class文件、字段表、方法表都可以携带自己的属性表集合。
三、字节码指令
1、字节码指令长度
操作码为8个字节,不对齐,有利于提高传输效率。
2、字节码与数据类型
iload指令用于从局部变量表中加载int型的数据到操作数栈。(fload则float)
虚拟机内部对不同数据类型的指令可能是同一种实现方式。
大部分指令都没有支持整数类型byte,char和short,编译器在编译器或运行期将这些类型数据零位扩展为响应的int类型数据。
3、加载和存储指令
iload_<n>:将一个局部变量加载到操作栈。
istore_<n>:将一个数值从操作数栈存储到局部变量表。
iconst<i>:将一个常量加载到操作数栈。
4、运算指令
5、类型转换指令
6、对象创建与访问指令
7、操作数栈管理指令
8、控制转移指令
9、方法调用和返回指令
10、异常处理指令
11、同步指令