【JVM规范笔记一】JVM虚拟机结构

只需要正确读取Class文件之中每一条字节码指令,并且能正确执行这些指令所蕴含的操作即可。所有在虚拟机规范之中没有明确描述的实现细节,都不应成为虚拟机设计者发挥创造性的牵绊,设计者可以完全自主决定所有规范中不曾描述的虚拟机内部细节,例如:运行时数据区的内存如何布局、选用哪种垃圾收集的算法、是否要对虚拟机字节码指令进行一些内部优化操作(如使用即时编译器把字节码编译为机器码)。 

Java虚拟机应有的共同外观:Class文件格式以及字节码指令集等。


1.class文件

一种平台中立(不依赖于特定硬件及操作系统的)的二进制格式来表示,并且经常(但并非绝对)以文件的形式存储,因此这种格式被称为Class文件格式。Class文件格式中精确地定义了类与接口的表示形式,包括在平台相关的目标文件格式中一些细节上的惯例,例如字节序(Byte Ordering)等。 

2.数据类型

Java虚拟机可以操作的数据类型可分为两类:原始类型(Primitive Types,也经常翻译为原生类型或者基本类型)和引用类型(Reference Types)。与之对应,也存在有原始值(Primitive Values)和引用值(Reference Values)两种类型的数值可用于变量赋值、参数传递、方法返回和运算操作。 

3.原始类型与值

原始数据类型包括了数值类型(Numeric Types)、布尔类型(Boolean Type )和returnAddress类型三类。其中数值类型又分为整型类型(Integral Types)和浮点类型(Floating-Point Types)两种,其中: 

整数类型包括: 

byte类型:值为8位有符号二进制补码整数,默认值为零。 

short类型:值为16位有符号二进制补码整数,默认值为零。 

int类型:值为32位有符号二进制补码整数,默认值为零。 

long类型:值为64位有符号二进制补码整数,默认值为零。 

char类型:值为使用16位无符号整数表示的、指向基本多文本平面(Basic Multilingual Plane)的Unicode值,以UTF-16编码,默认值为Unicodenull值('\u0000')。 

浮点类型包括: 

float类型:值为单精度浮点数集合中的元素,或者(如果虚拟机支持的话)是单精度扩展指数(Float-Extended-Exponent)集合中的元素。默认值为正数零。 

double类型:取值范围是双精度浮点数集合中的元素,或者(如果虚拟机支持的话)是双精度扩展指数(Double-Extended-Exponent)集合中的元素。默认值为正数零。 

布尔类型: 

boolean类型:取值范围为布尔值truefalse,默认值为false。 

returnAddress类型: 

returnAddress类型:表示一条字节码指令的操作码(Opcode)。在所有的虚拟机支持的原始类型之中,只有returnAddress类型是不能直接Java语言的数据类型对应起来的。 

returnAddress类型会被Java虚拟机的jsrretjsr_w指令所使用。returnAddress类型的值指向一条虚拟机指令的操作码

4.引用类型与值

Java虚拟机中有三种引用类型:类类型(Class Types)、数组类型(Array Types)和接口类型(Interface Types)。这些引用类型的值分别由类实例、数组实例和实现了某个接口的类实例或数组实例动态创建。 

其中,数组类型还包含一个单一维度(即长度不由其类型决定)的组件类型(Component Type),一个数组的组件类型也可以是数组。但从任意一个数组开始,如果发现其组件类型也是数组类型的话,继续重复取这个数组的组件类型,这样操作不断执行,最终一定可以遇到组件类型不是数组的情况,这时就把这种类型成为数组类型的元素类型(Element Type)。数组的元素类型必须是原始类型、类类型或者接口类型之中的一种。 

在引用类型的值中还有一个特殊的值:null,当一个引用不指向任何对象的时候,它的值就用null来表示。一个为null的引用,在没有上下文的情况下不具备任何实际的类型,但是有具体上下文时它可转型为任意的引用类型。引用类型的默认值就是null。 

Java虚拟机规范并没有规定null在虚拟机实现中应当怎样编码表示。 

5.运行时数据区

Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。 

PC寄存器 

每一条Java虚拟机线程都有自己的PCProgram Counter)寄存器。在任意时刻,一条Java虚拟机线程只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法(Current Method)。如果这个方法不是native的,那PC寄存器就保存Java虚拟机正在执行的字节码指令的地址,如果该方法是native的,那PC寄存器的值是undefinedPC寄存器的容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值。 

Java虚拟机栈 

每一条Java虚拟机线程都有自己私有的Java虚拟机栈(Java Virtual Machine Stack),这个栈与线程同时创建,用于存储栈帧(Frames)。Java虚拟机栈的作用与传统语言(例如C语言)中的栈非常类似,就是用于存储局部变量与一些过程结果的地方。另外,它在方法调用和返回中也扮演了很重要的角色。因为除了栈帧的出栈和入栈之外,Java虚拟机栈不会再受其他因素的影响,所以栈帧可以在堆中分配,Java虚拟机栈所使用的内存不需要保证是连续的。 

注意避免混淆StackHeapJava VMStackJava Heap的概念,Java虚拟机的实现本身是由其他语言编写的应用程序,在Java语言程序的角度上看分配在Java Stack中的数据,而在实现虚拟机的程序角度上看则可以是分配在Heap之中。 

如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量时,Java虚拟机将会抛出一个StackOverflowError异常。 

如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。 

Java堆 

堆(Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。 

如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常。 

方法区 

方法区(Method Area)是可供各条线程共享的运行时内存区域。方法区与传统语言中的编译代码储存区(Storage Area Of Compiled Code)或者操作系统进程的正文段(Text Segment)的作用非常类似,它存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法

如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常。 

运行时常量池 

运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池(Constant_Pool)的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表(Symbol Table)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。 

本地方法栈 


6.栈帧

栈帧(Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。 

栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。栈帧的存储空间分配在Java虚拟机栈之中,每一个栈帧都有自己的局部变量表(Local Variables)、操作数栈(Operand Stack)和指向当前方法所属的类的运行时常量池的引用。 

局部变量表 

一个局部变量可以保存一个类型为booleanbytecharshortfloatreferencereturnAddress的数据,两个局部变量可以保存一个类型为longdouble的数据。 

局部变量使用索引来进行定位访问,第一个局部变量的索引值为零,局部变量的索引值是从零至小于局部变量表最大容量的所有整数。 

当一个实例方法被调用的时候,第0个局部变量一定是用来存储被调用的实例方法所在的对象的引用(即Java语言中的“this”关键字),静态方法的局部变量表0位没有this引用

操作数栈 

每一个栈帧内部都包含一个称为操作数栈(Operand Stack)的后进先出(Last-In-First-OutLIFO)栈。栈帧中操作数栈的长度由编译期决定,并且存储于类和接口的二进制表示之中。在任意时刻,操作数栈都会有一个确定的栈深度,一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度。 

常量池引用----动态链接 

每一个栈帧内部都包含一个指向运行时常量池的引用来支持当前方法的代码实现动态链接(Dynamic Linking),在Class文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用。类加载的过程中将要解析掉尚未被解析的符号引用,并且将变量访问转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移量。 

由于动态链接的存在,通过晚期绑定(Late Binding)使用的其他类的方法和变量在发生变化时,将不会对调用它们的方法构成影响。 

7.对象的表示

Java虚拟机规范不强制规定对象的内部结构应当如何表示


8.浮点运算

9.初始化方法

Java语言中的构造函数是以一个名为的特殊实例初始化方法的形式出现的。通过Java虚拟机的invokespecial指令来调用,只有在实例正在构造的时候,实例初始化方法才可以被调用访问。

一个类或者接口最多可以包含不超过一个类或接口的初始化方法,类或者接口就是通过这个方法完成初始化的。这个方法是一个不包含参数的静态方法,名为。这个名字也是由编译器命名的,因为它并非一个合法的Java方法名字,不可能通过程序编码的方式实现。类或接口的初始化方法由Java虚拟机自身隐式调用,没有任何虚拟机字节码指令可以调用这个方法,只有在类的初始化阶段中会被虚拟机自身调用。 


10.异常

Java虚拟机里面的异常使用Throwable或其子类的实例来表示,绝大多数的异常的产生都是由于当前线程执行的某个操作所导致的,这种可以称为是同步的异常。与之相对的,异步异常是指在程序的其他任意地方进行的动作而导致的异常。Java虚拟机中异常的出现总是由下面三种原因之一导致的: 

1.虚拟机同步检测到程序发生了非正常的执行情况,这时异常将会紧接着在发生非正常执行情况的字节码指令之后抛出:

字节码指令所蕴含的操作违反了Java语言的语义,如访问一个超出数组边界范围的元素。 

类在加载或者链接时出现错误。 

使用某些资源的时候产生资源限制,例如使用了太多的内存。 

2. athrow字节码指令被执行。 

3. 由于以下原因,导致了异步异常的出现: 

调用了Thread或者ThreadGroupstop方法。 

Java虚拟机实现的内部程序错误。 

当某条线程调用了stop方法时,将会影响到其他的线程,或者在线程组中的所有线程。这时候其他线程中出现的异常就是异步异常,因为这些异常可能出现在程序执行过程的任何位置。虚拟机的内部异常也被认为是一种异步异常

11.字节码指令集

内容见书

12.类库

13.公有设计、私有实现



你可能感兴趣的:(jvm)