【发布时间】:2016-10-15 13:01:06
【问题描述】:
Java Virtual Machine Specification 声明 8 字节(例如 long 和 double)常量在 constant_pool 表中占用两个条目,这与其他每个仅占用一个条目的常量不同。该规范还提到这是一个糟糕的选择,但没有解释原因。
做出这一设计决定的最初原因是什么,当时的好处是什么?
【问题讨论】:
标签: java jvm constants .class-file
Java Virtual Machine Specification 声明 8 字节(例如 long 和 double)常量在 constant_pool 表中占用两个条目,这与其他每个仅占用一个条目的常量不同。该规范还提到这是一个糟糕的选择,但没有解释原因。
做出这一设计决定的最初原因是什么,当时的好处是什么?
【问题讨论】:
标签: java jvm constants .class-file
一个明确的答案需要与参与 Java 早期开发的人交谈。但是,我认为很明显,字节码格式最初设计时考虑了天真的解释器的性能。
考虑如何编写一个非常简单的 Java 字节码解释器。没有 JIT,没有优化等。您只需执行每条指令即可。假设常量池在加载时已被解码为 32 位值的表,像 ldc2_w x 这样引用常量池的指令将按照以下行执行 C 代码
*(*int64)(stack_ptr += 8) = *(*int64)(constant_pool_ptr + x * 4)
基本上,如果您在 32 位机器上,并且正在将所有内容转换为未优化的原始指针访问,那么使用两个插槽来存储 64 位值只是实现事物的逻辑方式。
今天它是一个糟糕的选择的原因是因为现在,解释器并不是完全没有像这样优化的。事实上,代码通常是 JITed。此外,64 位平台是标准,这意味着引用类型无论如何都占用 64 位*,即使规范将它们视为 32 位值。因此,这种 hack 不再有任何好处,但我们仍然要为规范和实现复杂性付出代价。
^ 至少在理论上。 JVM 默认使用 32 位压缩指针,即使在 64 位平台上也是如此,以减少内存使用。
【讨论】:
我不知道实际的答案,没有参与 Java 及其虚拟机的设计。我可以做出有根据的猜测。
常量池数组中充满了指向其他项的项;如果 long 和 double 常量被指向而不是包含在数组中,那么它们就不需要占用额外的空间。
由于它们确实占用了一个额外的索引,这意味着必须测试数组中的每个索引,以查看它是否是 long 或 double 索引之后的“不可用”索引。与仅检查范围相比,这会减慢对数组所有元素的访问速度。
从软件工程的角度来看,使用数组也是一种不自然的方式。对这个结构的每一个引用都需要包含关于这些索引的注释,以免一些额外的代码假设数组每个元素的大小是恒定的(这是关于数组的自然假设之一)。
进一步猜测,可能有人认为将空间用于指向只需要更多空间的结构的指针是一种耻辱,因此通过将其包含在数组本身中来节省空间。他们甚至可能认为这是使用 long 和 double 值的一种更有效的方式,却忽略了它会减慢其他所有内容的事实。
【讨论】:
他们使用 2 是因为这是他们在设计时决定的。没有理由它必须是 2。没有理由它不是 8 字节的 8。引用始终为 1,即使它们可能是 64 位的。
当时将 32 位以下的所有内容都视为 32 位是有道理的,并假设引用是 32 位这么长,而 double 是它的两倍,但鉴于它是一个虚拟机,这根本不重要。
两种方式的性能差异完全是想象的。
规范还提到这是一个糟糕的选择,但没有解释原因。
事后看来,最简单的事情就是让它成为所有东西的 1 个插槽,而不涉及在运行时如何使用内存。注意:JVM 可以优化掉变量,因此它们根本不占用任何空间。
【讨论】: