Java虚拟机不和包括Java在内的任何语言绑定,只与 "Class文件" 这种特定的二进制文件所关联, Class文件中包含了Java虚拟机指令集合符号表以及若干其它辅助信息。 Java虚拟机做为一个通用的、机器无关的执行平台,任何其余语言均可以将其做为语言的产品交付媒介。java
Class文件是一组以8位字节为基础的二进制流, 各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符, 这使得整个Class文件中存储的内容几乎所有是程序运行的必要数据,没有空隙存在。 当遇到须要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。数组
Class文件格式采用一种相似于C语言结构体的伪结构来存储数,这种伪结构有两种数据类型:安全
无符号数数据结构
表并发
无符号数:属于基本数据类型,以u一、u二、u四、u8来表明1个字节、2个字节、4个字节、8个字节的无符号数, 无符号数能够用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。编码
表:由多个无符号数或者其余表做为数据项构成的复合数据类型,全部表都习惯性地以"_info"结尾。 表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。spa
每一个Class文件的头四个字节称为魔数(Magic Number), 它的惟一做用是肯定这个文件是否为一个能被虚拟机接收的Class文件。 紧接着魔数的四个字节存储的是Class文件的版本号: 第五和第六是次版本号,第七和第八是主版本号。翻译
紧接着主次版本号以后的是常量池入口,常量池能够理解为Class文件之中的资源仓库, 它是Class文件结构中与其余项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一, 同时它仍是在Class文件中第一个出现的表类型数据项目。 常量池主要存放两大常量:字面量和符号引用。 字面量比较接近于java语言层面的的常量概念,如文本字符串、声明为final的常量值等。 而符号引用则属于编译原理方面的概念。包括下面三类常量:设计
类和接口的全限定名对象
字段的名称和描述符
方法的名称和描述符
因为常量池中常量的数量是不固定的,因此在常量池的入口须要设置一项u2类型的数据,表明常量池容量计数值。 与Java中语言习惯不同的是,这个容量技术是从1而不是0开始的,以下图表示,常量池容量(偏移地址:0x00000008)
为十六进制数0x0016,即十进制22,这表明常量池中有21项常量,索引值范围为1~21。在Class文件格式规范制定之时,设计者将第0项常量空出来是有特殊考虑的,这样作的目的在于 知足后面某些指向常量池的索引值的数据在特定状况下须要表达
"不引用任何一个常量池项目"的含义,这种状况就能够把索引值置为0来表示。 Class文件结构中只有常量池的容量计数从1开始, 对于其余集合类型,包括接口索引结合,字段表集合,方法表集合等容量技术都与通常习惯相同,从0开始。
在常量池结束以后,紧接着的两个字节表明访问标志, 这个标志用于识别一些类或者接口层次的访问信息, 包括:这个Class是类仍是接口,是否为public或者abstract类型,若是是类的话是否声明为final等等。 标志位和标志的含义对应以下:
类索引、父类索引与接口索引集合都按顺序排列在访问标志以后, Class文件由这三项数据来肯定这个类的继承关系。 类索引用于肯定这个类的全限定名, 父类索引用于肯定这个类的父类的全限定名,因为java语言的单继承,因此父类索引只有一个, 除了java.lang.Object以外,全部的java类都有父类,所以除了java.lang.Object外,全部java类的父类索引都不为0。 接口索引集合用来描述这个类实现了哪些接口,这些被实现的接口将按 implents(若是这个类自己是接口的话则是extends)后的接口顺序从左到右排列在 接口索引集合中。
字段表(field info)用于描述接口或类中声明的变量。 字段包括类变量和实例变量,但不包括在方法内部声明的局部变量。
咱们能够想想在Java中描述一个字段能够包含什么信息呢?
字段的做用域(public ,private,protected修饰符),是实例变量仍是类变量(static修饰符)、 可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、能否被序列化(transient修饰符)、 字段数据类型、字段名称。
上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。 而字段叫什么名字、字段被定义为何数据类型这些都是没法固定的,只能引用常量池中常量来描述。
Class文件存储格式中对方法的描述与对字段的描述几乎采用了彻底一致的方式。 方法表的结构如同字段表同样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
在Class文件,字段表,方法表中均可以携带本身的属性表集合,以用于描述某些场景专有的信息。 与Class文件中其它的数据项目要求的顺序、长度和内容不一样,属性表集合的限制稍微宽松一些, 再也不要求各个属性表具备严格的顺序,而且只要不与已有的属性名重复, 任何人实现的编译器均可以向属性表中写入本身定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。
在java虚拟机的指令集中,大多数的指令都包含了其操做所对应的数据类型信息, 例如iload指令用于从局部变量表中加载int类型的数据到操做数栈中,而fload指令加载的则是float类型的数据。 这两条指令的操做在虚拟机内部多是同一段代码实现的,但在Class文件中它们必须拥有各自独立的操做码。
大部分的指令都没有支持整数类型byte、char、short甚至没有任何指令支持boolean类型。 大多数对于byte、char、short、boolean类型的操做,实际上都是使用相应的int类型做为运算符类型。
加载和存储指令用于将数据在栈帧中的局部变量表和操做数栈之间来回传输。
运算或算术指令用于对操做数栈上的值进行某种特定运算,并把结果从新存入操做栈顶。
大致上算术指令能够分为两种: 对整型数据和对浮点数据进行运算指令。 (因为没有byte、char、short、boolean类型,因此对这类数据的运算应使用int类型指令代替)
类型转换指令能够将两种不一样的数值类型进行相互转换。 (好比int类型转换为float类型) 小范围到大范围类型安全转换,无需显式的转换指令,不然必须显式的使用转换指令来完成。
虽然类实例和数组都是对象,但java虚拟机对类实例和数组的建立和操做使用了不一样的字节码指令。
如同操做数据结构中的栈同样,Java虚拟机也提供了一些用于直接操做操做数栈的指令。
能够认为控制转移指令就是在有条件或无条件地修改PC寄存器的值。
invokevirtual 指令用于调用对象的实例方法 invokeinterface 指令用于调用接口方法 invokespecial 指令用于调用一些须要特殊处理的实例方法 invokestatic 指令用于调用类方法(static方法) invokedynamic 指令用于在运行时动态解析出调用点限定符所使用的方法。
方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的。
在java虚拟机中,处理异常(catch语句)不是由字节码指令来实现的,而是采用异常表的方式。
java虚拟机能够支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构使用管程(Monitor)来支持的。
将输入的Java虚拟机代码在加载或执行时翻译成另一种虚拟机的指令集。
将输入的Java虚拟机代码在加载或执行时翻译成宿主主机CPU的本地指令集。(即JIT代码生成技术)
Class文件结构已经有十多年历史了,这10多年间,java技术体系有了翻天覆地的变化,可是Class文件结构一直处于比较稳定的状态,Class文件的主体结构、字节码指令的语义和数量几乎没有出现过变更, 全部Class文件格式的改进,都集中在向访问标志、属性表这些在设计上就可扩展的数据结构中添加内容。