如https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html#jvms-2.11.1 所述,将操作数类型编码为操作码是有代价的:
鉴于 Java 虚拟机的一字节操作码大小,将类型编码为操作码会给指令集的设计带来压力。如果每个类型化指令都支持 Java 虚拟机的所有运行时数据类型,那么指令数量将超过一个字节所能表示的数量。
因此,似乎应该只对需要操作数类型信息或启用优化的指令执行此操作。例如,需要区分iadd和fadd,因为整数和浮点数的加法实现方式不同。而且我不知道为什么有不同的指令从数组(分别为和)加载 aboolean和 an ,但我至少可以想象一些性能原因。intbaloadiaload
但是,为什么将int( istore) 和float( fstore)存储到局部变量中有不同的指令?它们不应该以完全相同的方式实施吗?
这个答案/sf/answers/184670041/说字节码验证器需要输入指令。但这真的有必要吗?在一个方法中,所有数据都从方法的参数(类型已知)和类字段(类型也已知)流向其他类字段和返回值。因此,由于输入和输出的类型是已知的,我们不能为指令重建任何缺失的类型吗?事实上,这不是字节码验证器所做的,因为它必须检查类型,即它必须知道需要哪些类型?
简而言之:如果我们将istore和组合fstore成一条指令会破坏什么?性能或便携性会受到影响吗?字节码验证会停止工作吗?
istore并且fstore在我曾经使用过的几乎每个 JVM 和每个架构上都以不同的方式实现。
例如,在 HotSpot JVM x64 解释器中,istore_0实现为
mov dword ptr [r14], eax
Run Code Online (Sandbox Code Playgroud)
而fstore_0实现为
movss dword ptr [r14], xmm0
Run Code Online (Sandbox Code Playgroud)
解释器将栈顶值缓存在一个寄存器中,整数和浮点值有不同的寄存器。
类似地,baload并且iaload实现方式不同,因为它们使用不同的偏移乘数(分别为 1 和 4),并且需要不同的机器指令来加载 8 位值与 32 位值。
正如您所注意到的,一些类型信息可以从数据流分析中导出,字节码验证器无论如何都会这样做。但是为了在运行时使用这些信息,堆栈槽和局部变量需要以某种方式用相应的类型标记,并且合并的字节码指令需要在运行时读取这个标记并根据标记进行调度。当然,这在运行时性能和使用的内存方面都不是最优的。