为什么长整型和双精度型在 Java 类的常量池中占据两个条目?



Java虚拟机规范规定,8字节(如longdouble)常量在constant_pool表中占用两个条目,而其他常量每个只占用一个条目。该规范还提到这是一个糟糕的选择,但没有解释原因。

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

一个明确的答案需要与参与Java早期开发的人交谈。然而,我认为很明显,字节码格式最初设计时就考虑到了简单解释器的性能。

考虑如何编写一个非常简单的Java字节码解释器。没有JIT,没有优化,等等。你只要执行每条指令就行了。假设常量池在加载时已被解码为32位值的表,则像ldc2_wx这样引用常量池的指令将沿着

一行执行C代码

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

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

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

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

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

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

由于它们占用了一个额外的索引,这意味着必须测试数组中的每个索引,以确定它是否是在long型或double型索引之后的"不可用"索引。与只检查range相比,这大大降低了对数组所有元素的访问速度。

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

进一步猜测,可能有人认为占用指向结构体的指针的空间是一件可耻的事情,而结构体只需要多一点空间,因此通过将其包含在数组本身中来节省空间。他们甚至可能认为这是一种更有效的使用long和double值的方法,而忽略了这样做会减慢其他所有操作的速度。

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

在当时把32位以内的所有东西都当作32位是有意义的,并且假设reference是32位,那么长,double是double,但是考虑到它是一个虚拟机,这根本无关紧要。

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

规范还提到这是一个糟糕的选择,但没有解释原因。

事后看来,最简单的方法是将所有内存设置为1个插槽,而不涉及运行时如何使用内存。注意:JVM可以优化变量,这样它们就不会占用任何空间。

最新更新