Java虚拟机概念——随手记
一、虚拟机结构
1.在Java语言中设计boolean类型值的运算,在编译之后都使用Java虚拟机中的int数据类型来代替,具体是true值采用数值1进行表示,false值采用数值0进行表示;在Oracle的虚拟机实现里,boolean数组将会编码成byte数组,每个boolean元素占8位。
2.null并没有被规定在虚拟机实现中应当怎样编码表示。
3.pc(program counter)寄存器,执行方法是native则值为null,否则就保存Java虚拟机正在执行的字节码指令的地址。
4.Java虚拟机栈,也称Java栈,每个虚拟机线程私有,用于存储栈帧。StackOverflowError,线程请求分配的栈容量超过Java栈允许的最大容量;OutOfMemoryError,内存不足以扩展栈容量或创建新的Java栈。
5.Java堆,可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。在虚拟机启动时创建,存储了被GC所管理的各种对象。OutOfMemoryError,实际所需的堆超过了自动内存管理系统能提供的最大容量。
6.方法区,可供各个线程共享的运行时内存区域。存储每一个类的结构信息,例如,运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。OutOfMemoryError,方法区的内存空间不能满足内存分配请求。
7.运行时常量池,class文件中每一个类或接口的常量池表的运行时表示形式,包括了若干种不同的常量,从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。OutOfMemoryError,创建类或接口时构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值。
8.本地方法栈,使用到传统的栈(通常称为C Stack)来支持native方法的执行,这个栈便是本地方法栈。StackOverflowError和OutOfMemoryError与Java栈类似。
9.栈帧,用来存储数据和部分过程结果的数据结构,同时也用来处理动态链接、方法返回值和异常分派。栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一个线程的栈帧。
10.局部变量表,每个栈帧内部都包含一组称为局部变量表的变量列表。当调用一个实例方法时,第0个局部变量一定是用来存储被调用的实例方法所在的对象的引用(this关键字)。
11.操作数栈,LIFO(后进先出)栈,栈深度确定,long或double占用两个单位的栈深度,其他类型占一个单位的栈深度。
12.动态链接,每个栈帧内部都包含一个指向运行时常量池的引用来支持当前方法的代码实现动态链接。
13.方法正常调用完成,执行过程中没有抛出任何异常;方法异常调用完成,抛出的异常没有在该方法中处理或在执行过程中遇到athrow字节码指令并显式的抛出异常同时在该方法内部没有捕获异常。一定不会有方法返回值返回给其调用者。
14.Java虚拟机规范不强制规定对象的内部结构应当如何表示。
15.Java虚拟机中的浮点操作遇到非法操作,如被零整除,上限溢出、下限溢出和非精确时,不会抛出exception、trap或者IEEE 754异常情况中定义的其他信号。
Java虚拟机不支持IEEE 754中的信号浮点比较。
在Java虚拟机中,舍入操作永远使用IEEE 754标准中定义的向最接近数舍入模式,无法精确表示的结果将会舍入为最接近的可表示值来保证此值的最低有效位为0.
Java虚拟机不支持IEEE 754的单精度扩展和双精度扩展格式,但是在双精度浮点数集合和双精度扩展指数集合范围与单精度扩展指数格式的表示会有重叠。
16.浮点模式,每个方法都有一项属性称为浮点模式,FP-strict模式和非FP-strict模式。方法的浮点模式取决于class文件中代表该方法的method_info结构的访问标志(access_flags)中的ACC_STRICT标志位的取值。
17.数值集合转换,在一些特定场景下,支持扩展指数集合的Java虚拟机实现数值在标准浮点数集合与扩展指数集合之间的映射关系是允许或必要的,这种映射操作就成为数值集合转换。数值集合转换必须保证在转换过程中不会改变正负无穷和NaN的符号,对于一个非浮点类型的数值,数值集合转换是无效的。
18.特殊方法,构造器是以一个名为的特殊实例初始化方法的形式出现的,通过虚拟机的invokespecial指令调用。一个类或者接口最多可以包含不超过一个类或接口的初始化方法,是一个不包含参数的、返回类型为void的方法。
签名多态性:通过java.lang.invoke.MethodHandle类进行声明;只有一个类型为Object[]的形参;返回值为Object;ACC_VARARGS和ACC_NATIVE标志被设置。
19.异常,抛异常的本质实际上是程序控制权的一种即时的、非局部的转换——从异常抛出的地方转换至处理异常的地方。
Java虚拟机中异常的出现总是由下面三种原因之一导致的:athrow字节码指令被执行;虚拟机同步检测到程序发生了非正常的执行情况,这时异常将会紧接着在发生非正常执行情况的字节码指令之后抛出;调用Thread或者ThreadGroup的stop方法,Java虚拟机实现的内部程序错误发生。
20.字节码指令集,Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码以及跟随其后的零至多个代表此操作所需参数的操作数所构成。
20.1.数据类型,编译器会在编译期或运行期将byte和short类型的数据带符号扩展为相应的int类型数据,将boolean和char类型数据零位扩展为相应的int类型数据。
20.2.加载和存储指令,用于将数据从栈帧的本地变量表和操作数栈之间来回传递。
20.3.算术指令,用于对两个操作数栈上的值进行某种特定运算,并把结构重新压入操作数栈。只有除法指令以及求余指令出现除数为零时会导致虚拟机抛出异常。Java虚拟机在处理浮点类型数运算时,不会抛出任何运行时异常,当一个操作产生溢出时,将会使用有符号的无穷大来表示,如果某个操作结果没有明确的数学定义,将会使用NaN值来表示。
20.4.类型转换指令,宽化类型转换指令包括:i2l、i2f、i2d、l2f、l2d、f2d. 尽管宽化类型转换实际上是可能发生精度丢失的,但是这种转换永远不会导致Java虚拟机抛出运行时异常;窄化类型转换可能会导致转换结果产生不同的正负号、不同的数量级,转换过程很可能会导致数值丢失精度。
20.5.对象创建与操作,虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。
20.6.操作数栈管理指令,Java虚拟机提供了一些用于直接操作数栈的指令,包括:pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2和swap。
20.7.控制转移指令,可以让Java虚拟机有条件或无条件地从指定指令而不是控制转移指令的下一条指令继续执行程序。
20.8.方法调用和返回指令,方法调用:invokevirtual、invokeinterface、invokespecial、invokestatic、invokedynamic,方法返回指令根据返回值的类型进行区分。
20.9.抛出异常,在程序中显式抛出异常的操作由athrow指令实现,除了这种情况,还有别的异常会在其他Java虚拟机指令检测到异常状况时由虚拟机自动抛出。
20.10.同步,Java虚拟机可以支持方法级的同步和方法内部的一段指令序列的同步,这两种同步结构都是使用同步锁来支持的。方法级的同步是隐式的,即无需通过字节码指令来控制。结构化锁定是指在方法调用期间每一个同步锁退出都与前面的同步锁进入相匹配的情形。因为无法保证所有提交给Java虚拟机执行的代码都满足结构化锁定,所以Java虚拟机允许通过以下两条规则来保证结构化锁定成立。假设T代表一个线程,M代表一个同步锁,那么:①T在方法执行时持有同步锁M的次数必须与T在方法完成时释放的同步锁M的次数相等。②在方法调用过程中,任何时刻都不会出现线程T释放同步锁M的次数比T持有同步锁M次数多的情况。
21.类库,Java虚拟机必须对不同平台下Java类库的实现提供充分的支持。可能需要的类库:java.lang.reflect,ClassLoader,类或接口的连接和初始化,安全,多线程,弱引用。
22.公有设计、私有实现,①将输入的Java虚拟机代码在加载时或执行时翻译成另外一种虚拟机的指令集,②将输入的Java虚拟机代码在加载时或执行时翻译成宿主机CPU的本地指令集(有时称Just-In-Time代码生成或JIT代码生成)。
23.虚拟机汇编语言格式:<index><opcode>[<operand1>[<operand2>...]][<comment>],<index>是code数组中指令的操作码的索引,<opcode>为指令的操作码的助记符号,<operandN>是指令的操作数,<comment>为行尾的语法注释。在每一行中,在表示运行时常量池索引的操作数前,会以井号(’#’)开头,在指令后的注释中,会带有这个操作数的描述。
24.常量、局部变量和控制结构的使用,Java虚拟机是基于栈架构设计的,Java虚拟机的大多数操作是从当前栈帧的操作数栈取出1个或多个操作数,或将结果压入操作数栈中。每调用一个方法,都会创建一个新的栈帧,并创建对应方法所需的操作数栈和局部变量表。
25.算术运算,Java虚拟机通常基于操作数栈进行算术运算(只有iinc指令例外,它直接对局部变量进行自增操作)。算术运算使用到的操作数都是从操作数栈中弹出的,运算结果被压回操作数栈中。
26.访问运行时常量池,int、long、float、和double类型的数据以及表示String实例的引用类型数据的访问将由ldc、ldc_w、ldc2_w指令实现。ldc和ldc_w指令用于访问运行时常量池中的对象,包括类String的实例,但不包括double和long类型的值。当使用的运行时常量池的项的数目过多时,需要使用ldc_w指令取代ldc指令来访问常量池。ldc2_w指令用于访问类型为double和long的运行时常量池项,这个指令没有非宽索引的版本,即没有ldc2指令。