2.7 对象的表示
java虚拟机并不要求对象满足任何特定的内部结构。
在Oracle的一些Java虚拟机实现中,对类实例的引用是指向句柄的指针,该句柄本身是一对指针:一个指向包含对象方法的表和指向表示Class对象的指针对象的类型,另一个是从堆为对象数据分配的内存。
2.8 浮点算法
Java虚拟机包含IEEE二进制浮点运算标准(ANSI / IEEE Std.754-1985,New York)中规定的浮点运算的子集。
2.8.1 Java虚拟机浮点算法和IEEE 754
Java虚拟机支持的浮点运算与IEEE 754标准之间的主要区别是:
- IEEE 754中的定义的无效操作,如被0除,溢出,向下溢出或者不精确,在java虚拟机中不会抛出异常,或者其它IEEE 754中规定的表示方式。java虚拟机没有型号NaN值。(The Java Virtual Machine has no signaling NaN value. )
- JAVA虚拟机中不支持IEEE 754中的信号浮点比较。(The Java Virtual Machine does not support IEEE 754 signaling floating-point comparisons. )
- Java虚拟机的舍入操作始终使用IEEE 754舍入到最近模式。不精确的结果四舍五入到最接近的可表示值,并且保证此值的最低有效位为0,这是IEEE 754默认模式。但Java虚拟机指令将浮点类型的值转换为整数类型的值,向零舍入。Java虚拟机没有提供任何改变浮点舍入模式的方法。
- Java虚拟机不支持IEEE 754单扩展或双扩展格式,但是在双精度浮点数集合和双精度扩展指数集合范围与单精度扩展指数格式的表示会有重叠。单精度扩展指数和双精度扩展指数值集(可选择支持)与IEEE 754扩展格式的值不对应:IEEE 754扩展格式规定了扩展精度以及扩展指数范围。
2.8.2 浮点格式
每一个方法都有一个浮点模式,要么是FP-strict,要么时not FP-strict。方法的浮点格式由方法的method_info结构中access_flags项的ACC_STRICT标志来决定。如果设置了这个标志位就是FP-strict,否则就是not FP-strict。
请注意,ACC_STRICT标志的这种映射意味着由JDK 1.1版或更早版本中的编译器编译的类中的方法实际上时not FP-strict。
当调用创建包含操作数堆栈的帧的方法具有浮点模式时,我们将操作数堆栈称为具有给定的浮点模式。类似地,当包含该指令的方法具有该浮点模式时,我们将该Java虚拟机指令称为具有给定的浮点模式。
如果支持单精度扩展指数集(第2.3.2节),则非FP-strict的操作数堆栈上float类型的值可以超出该值集,除非值集转换禁止(§2.8.3))。如果支持双精度扩展指数集(第2.3.2节),则非FP-strict的操作数堆栈上的double类型值可以超出该值集,除非值集转换禁止。
在所有其他上下文中,无论是在操作数堆栈上还是在其他位置上,无论浮点模式如何,float和double类型的浮点值只能分别在单精度浮点集和双精度浮点集的范围内。特别是,类和实例字段、数组元素、局部变量和方法参数只能从标准值集中取值。
2.8.3 数值集转换
在一些特定场景下,支持扩展指数集合的 Java 虚拟机实现数值在标准浮点数集合与扩展指数集合之间的映射关系是允许和必要的,这种映射操作就称为数值集合转换数值集合转换并非数据类型转换,而是在同一种数据类型之中不同数值集合的映射操作。
在指示值集转换的情况下,允许实现对值执行以下操作之一:
- 如果一个值的类型为float,并且不是float值集的元素,则它将值映射到float值集的最近元素。
- 如果值的类型为double且不是double值集的元素,则它将值映射到double值集的最近元素。
另外,如果如果要进行数值集转换,则需要执行下面的操作:
- 假设非FP-strict的Java虚拟机指令执行时导致了float类型的值被推送到FP-strict的操作数堆栈,如作为参数传递,或存储到局部变量,字段或数组的元素。如果该值不是floa值集的元素,则将该值映射到float值集的最近元素。
- 假设非FP-strict的Java虚拟机指令执行时导致了double类型的值被推送到FP-strict的操作数堆栈,如作为参数传递,或存储到局部变量,字段或数组的元素。如果该值不是double值集的元素,则将该值映射到double值集的最近元素。
在方法调用期间传递浮点类型的参数(包括本地方法调用)可能会发生这种所需的值集转换,如:将浮点类型的值从非FP-strict的方法返回到FP-strict的方法、或者将浮点类型的值存储到非FP严格的方法中的局部变量,字段或数组中。
并非扩展指数值集中的所有值都可以精确映射到相应标准值集中的值。如果映射的值太大而无法准确表示(其指数大于标准值集允许的值),则将其转换为相应类型的(正或负)无穷大。如果映射的值太小而不能精确表示(其指数小于标准值集所允许的值),则将其舍入到最接近的可表示的非规范化值或相同符号的零。
数值集转换不会转换无限大、无限小和NaN并且不会改变值的符号。
2.9 特殊方法
从java虚拟机的维度来看,每一个使用java编程语言编写的构造器都是一个实例初始化方法(instance initialization method),并且有一个特殊的名字<init>。这个名称有编译器提供,因为<init>在java编程语言中不是一个合法的标识符,无法使用java编程语言编写。实例初始化方法只能被java虚拟机通过指令调用,并且只会被调用在未初始化的实例上。实例初始化方法和构造器的访问权限一致。
一个类或者接口最多只有一个类或接口初始化方法(class or interface initialization method),通过调用这个初始化方法来初始化这个类或者接口。类或者接口的初始化方法有一个特殊的名称<clinit>,它没有参数,并且返回值为void。
类文件中名为<clinit>的其他方法无关紧要。 它们不是类或接口初始化方法。 它们不能被任何Java虚拟机指令调用,并且永远不会被Java虚拟机本身调用。
在版本号为51.0或以上的类文件中,方法必须另外设置ACC_STATIC标志(§4.6),以便成为类或接口初始化方法。
这个需求是在Java SE 7中引入的。在版本号为50.0或更低的类文件中,一个名为<clinit>的方法返回值是void的,不接受任何参数,不管它的ACC_STATIC标志设置如何,都被认为是类或接口初始化方法。
<clinit>名称由编译器提供,因为这不是一个有效的标识符,它不能在java编程语言直接使用。类和接口的初始化方法被java虚拟机隐式调用,它们从来不会被java虚拟机指令直接调用,而是作为类初始化过程中一部分被间接调用。
如果下列条件都为真,则方法为签名多态(signature polymorphic):
- 它在java.lang.invoke.MethodHandle类中声明
- 它只有一个Object[] 参数
- 它的返回类型是Object
- 它的设置了
ACC_VARARGS和
ACC_NATIVE
标志位
在Java SE 8中,仅有的签名多态方法( signature polymorphic)是java.lang.invoke.MethodHandle类中
的invoke
和 invokeExact方法。
Java虚拟机在invokevirtual指令中对签名多态方法进行了特殊处理,以实现方法句柄(method handle)的调用。方法句柄是对底层方法、构造函数、字段或类似低级操作(§5.4.3.5)的强类型,直接可执行的引用,具有可选的参数或返回值转换。这些转换非常普遍,包括转换、插入、删除和替换等模式。可以从Java SE平台API中的java.lang.invoke包中获取更多信息。