为什么 long 和 double 在 Java 类' 常量池中占用两个条目?

Why do longs and doubles take up two entries in Java classes' constant pools?

Java Virtual Machine Specification 声明 8 字节(例如 longdouble)常量在 constant_pool table 中占用两个条目,不像其他每个只占用一个条目的常量。该规范还提到这是一个糟糕的选择,但没有解释原因。

这个设计决定背后的最初原因是什么,当时有什么好处?

我不知道实际答案,因为我没有参与 Java 及其虚拟机的设计。我可以做出有根据的猜测。

常量池数组充满了指向其他项的项;如果 long 和 double 常量被指向而不是包含在数组中,那么它们就不需要占用额外的空间。

因为它们确实占用了一个额外的索引,这意味着必须测试数组中的每个索引,看它是否是 long 或 double 索引之后的 'unusable' 索引。与仅检查范围相比,这会显着降低对数组所有元素的访问速度。

从软件工程的角度来看,使用数组也是一种不自然的方式。对该结构的每个引用都需要包含关于这些索引的注释,以免一些额外的代码假定数组的每个元素具有恒定大小(这是关于数组的自然假设之一)。

进一步猜测,可能有人认为占用 space 用于指向只需要多一点 space 的结构的指针是一种耻辱,因此保存了 [=22] =] 通过将其包含在数组本身中。他们甚至可能认为这是使用 long 和 double 值的一种更有效的方法,而忽略了它会减慢其他一切的事实。

一个明确的答案需要与参与 Java 早期开发的人交谈。但是,我认为很明显,字节码格式最初设计时考虑的是朴素解释器的性能。

考虑如何编写一个非常简单的 Java 字节码解释器。没有 JIT,没有优化等。您只需执行每条指令即可。假设常量池在加载时已被解码为 32 位值的 table,像 ldc2_w x 这样引用常量池的指令将按照

行执行 C 代码

*(*int64)(stack_ptr += 8) = *(*int64)(constant_pool_ptr + x * 4)

基本上,如果您在 32 位机器上,并且正在将所有内容转换为未经优化的原始指针访问,那么为 64 位值使用两个槽是实现事物的简单逻辑方法。

今天它是一个糟糕的选择的原因是因为现在,口译员并不是像这样完全没有优化。事实上,代码通常是 JITed 的。此外,64 位平台是常态,这意味着引用类型无论如何都占用 64 位*,即使规范将它们视为 32 位值。因此,这种 hack 不再有任何好处,但我们仍然付出了规范和实现复杂性的代价。

^ 至少理论上是这样。 JVM 默认使用 32 位压缩指针,即使在 64 位平台上也是如此,以减少内存使用。

他们使用 2 是因为这是他们在设计时决定的。没有理由必须是 2。没有理由不是 8 个字节的 8。引用始终为 1,即使它们可能是 64 位的。

当时将 32 位以下的所有内容都视为 32 位并假设引用是 32 位那么长并且双倍是它的两倍是有意义的,但考虑到它是虚拟机,这根本无关紧要.

两种方式的性能差异完全是名义上的。

The specification also mentions that it was a poor choice but doesn't explain why.

事后看来,最简单的事情就是让它成为所有东西的 1 个插槽,而不涉及在运行时如何使用内存。注意:JVM 可以优化掉变量,所以它们根本不带任何 space。