问题
JVM Run-time Data Areas separate stack for each method being executed. It contains operand stack and local variables. Every time you load a variable, you need to const
to the operand stack and then store
to the local variables. Why not directly operate the local variable table, and have some seemingly repeated work?
回答1:
An instruction set with direct operands has to encode the operands in each instruction. In contrast, with an instruction set using an operand stack, the operands are implicit.
The advantage of implicit arguments is not obvious when looking at a small trivial operation like loading a constant into a variable. This example is comparing an “opcode, constant, opcode, variable-index” sequence with “opcode, constant, variable index”, so it seems like addressing directly is simpler and more compact.
But let’s look at, e.g. return Math.sqrt(a * a + b * b);
Assuming the variable indices start at zero, the bytecode looks like
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
For a directly addressing architecture, we would need something like
dmul a,a → tmp1
dmul b,b → tmp2
dadd tmp1,tmp2 → tmp1
invokestatic #2 tmp1 → tmp1
dreturn tmp1
where we have to replace the names with indices.
While this sequence consists of fewer instructions, each instruction has to encode its operands. When we want to be able to address 256 local variables, we need a byte per operand, so each arithmetic instruction needs three bytes plus opcode, the invocation needs two plus opcode and method address, and the return needs one plus opcode. So for instructions at byte boundaries, this sequence needs 19 bytes, significantly more than the equivalent Java bytecode, while being limited to 256 local variables whereas the bytecode supports up to 65536 local variables.
This demonstrates another strength of the operand stack concept. Java bytecode allows to combine different, optimized instructions, e.g. for loading an integer constant there are iconst_n
, bipush
, sipush
, and ldc
and for storing it into a variable there are istore_n
, istore n
, and wide istore n
. An instruction set with direct variable addressing would need distinct instructions for each combination when it is supposed to support a wide range of constants and numbers of variables but still support compact instructions. Likewise, it would need multiple versions of all arithmetic instructions then.
Instead of a three operand form, you could use a two operand form, where one of the source variables also indicates the target variable. This results in more compact instructions but creates the need for additional transfer instructions if the operand’s value is still needed afterwards. The operand stack form still is more compact.
Keep in mind that this only describes the operations. An execution environment is not required to strictly follow this logic when executing the code. So besides the simplest interpreters, all JVM implementations convert this into a different form before executing, so the original stored form doesn’t matter for the actual execution performance. It only affects the space requirements and loading time, which both benefit from a more compact representation. This especially applies to code transferred over potentially slow network connections, one of the use cases, Java was originally designed for.
来源:https://stackoverflow.com/questions/61302770/what-is-the-role-of-operand-stack-in-jvm