JVM中的操作数栈是什么作用?

3

JVM运行时数据区域为每个正在执行的方法单独创建一个栈。它包含操作数栈和局部变量。每次加载变量时,需要将其const到操作数栈,然后store到局部变量中。为什么不直接操作局部变量表,并且还要做一些看似重复的工作呢?


5
因为这就是堆栈机的工作原理。相比于基于寄存器的系统,它更容易实现(无论是编译器还是解释器),并且编码非常紧凑。但是对于高效解释来说效果不佳,尽管在90年代不是那么重要。不过我担心这个问题对于SO来说太过广泛和基于观点。 在非常粗略的瞥一眼下,维基百科的文章看起来很合理,可能是一个很好的开始。如果你对编译器感兴趣,《龙书》会是一个不错的选择。 - Voo
1
const到操作数栈”是什么意思?你是指load指令吗?这是基于堆栈的架构:它们就是这样工作的。 - user207421
1个回答

6

具有直接操作数的指令集必须在每条指令中编码操作数。相反,使用操作数堆栈的指令集中,操作数是隐含的。

隐式参数的优点在于对于像将常量加载到变量这样的小型简单操作来说并不明显。这个例子比较了一个“操作码、常量、操作码、变量索引”序列和“操作码、常量、变量索引”,所以看起来直接寻址更简单更紧凑。

但是让我们看一下例如 return Math.sqrt(a * a + b * b); 这样的代码。

假设变量索引从零开始,字节码如下:

   0: dload_0
   1: dload_0
   2: dmul
   3: dload_2
   4: dload_2
   5: dmul
   6: dadd
   7: invokestatic  #2                  // Method java/lang/Math.sqrt:(D)D
  10: dreturn
  11 bytes total

对于直接寻址架构,我们需要像这样的东西

dmul a,a → tmp1
dmul b,b → tmp2
dadd tmp1,tmp2 → tmp1
invokestatic #2 tmp1 → tmp1
dreturn tmp1

这里我们需要用索引替换名称。

尽管这个序列包含的指令较少,但每个指令都必须对其操作数进行编码。当我们想要寻址256个本地变量时,我们需要每个操作数一个字节,因此每个算术指令需要三个字节加上操作码,调用需要两个加上操作码和方法地址,而返回需要一个加上操作码。因此,在字节边界处的指令中,这个序列需要19个字节,比等效的Java bytecode多得多,同时只能支持256个本地变量,而bytecode支持最多65536个本地变量。

这展示了操作数堆栈概念的另一个优势。Java bytecode允许组合不同的优化指令,例如,为了加载整数常量,有iconst_nbipushsipushldc,为了将其存储到变量中,有istore_nistore nwide istore n。如果一个具有直接变量寻址的指令集想要支持各种常量和变量数量的宽范围,同时还要支持紧凑的指令,那么它就需要针对每个组合使用不同的指令。同样,它还需要多个版本的所有算术指令。

可以使用两个操作数的形式代替三个操作数的形式,其中一个源变量也表示目标变量。这会产生更紧凑的指令,但如果操作数的值仍然需要后续使用,则需要额外的传输指令。操作数堆栈形式仍然更加紧凑。

请记住,这只描述了操作。执行环境在执行代码时并不需要严格遵循这个逻辑。因此,除了最简单的解释器之外,所有JVM实现都会在执行之前将其转换为不同的形式,因此原始存储形式对于实际的执行性能并不重要。它只影响空间要求和加载时间,两者都受益于更紧凑的表示形式。这尤其适用于通过潜在缓慢的网络连接传输的代码,这是Java最初设计的用例之一。


2
@jiangyongbing24,您不接受答案的原因是什么? - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接