What is an operand stack?
Operand stack holds the operand used by operators to perform operations. Each entry on the operand stack can hold a value of any Java Virtual Machine type.
From JVM specifications,
Java Virtual Machine instructions take operands from the operand stack, operate on them, and push the result back onto the operand stack. The operand stack is also used to prepare parameters to be passed to methods and to receive method results.
For example, iadd
instruction will add two integer values, so it will pop top two integer values from operand stack and will push result into operand stack after adding them.
For more detailed reference, you can check JVMS#2.5 : Run-Time Data Areas
Summarizing it in context of Operand stack,
_______________________________
| _____________________ |
| | + --------+ | |
| JVM | | Operand | | |
| Stack | FRAME | Stack | | |
| | +---------+ | |
| |_____________________| |
|_______________________________|
- JVM supports multithreaded execution environment. Each thread of execution has its private java virtual machine stack (JVM Stack) created at the same time of thread creation.
- This Java virtual machine stack stores frames. Frame holds data, partial results, method return values and performs dynamic linking.
- Each frame contains stack, known as Operand stack, which holds the operand values of JVM types. A depth of operand stack is determined at compile time and updated with operators.
But could's not understand exactly that what it is and how it works in jvm?
The JVM defines virtual computer, and the instruction set of that computer is stack based. What this means is that instructions in the JVM instruction set will typically push and pop operands from the stack. So for example,
- a load instruction might fetch a value from a local variable, instance variable or class variable and push it onto the operand stack,
- an arithmetical instruction will pop values from the operand stack, perform the computation and push the result back onto the stack, and
- a store instruction will pop a value from the stack and store it ...
@T.J.Crowder's answer gives a more concrete example in lots of detail.
How the operand stack is implemented is platform specific, and it depends on whether code is being interpreted or whether it has been JIT compiled.
In the interpreted case, the operand stack is probably an array that is managed by the interpreter code. The push and pop micro-operations would be implemented something like:
stack[top++] = value;
and
value = stack[--top];
When the code is JIT compiled, the bytecode instruction sequences are transformed into native instruction sequences that achieve the same thing as the bytecodes did. The operand stack locations get mapped to either native registers or memory locations; e.g. in the current native stack frame. The mapping involves various optimizations that are aimed at using registers (fast) in preference to memory (slower).
Thus in the JIT compiled case, the operand stack no longer has a clear physical existence, but the overall behaviour of the compiled program is the same as if the operand stack did exist1.
1 - Actually, it may not be exactly the same when you take the Java memory model into account. However, the memory model places clear boundary on what the differences may be. And in the case of a single threaded computation that doesn't interact with the outside (e.g. I/O, clocks, etc), there can be no observable differences.
It's how the various individual bytecode operations get their input, and how they provide their output.
For instance, consider the iadd
operation, which adds two int
s together. To use it, you push two values on the stack and then use it:
iload_0 # Push the value from local variable 0 onto the stack
iload_1 # Push the value from local variable 1 onto the stack
iadd # Pops those off the stack, adds them, and pushes the result
Now the top value on the stack is the sum of those two local variables. The next operation might take that top stack value and store it somewhere, or we might push another value on the stack to do something else.
Suppose you want to add three values together. The stack makes that easy:
iload_0 # Push the value from local variable 0 onto the stack
iload_1 # Push the value from local variable 1 onto the stack
iadd # Pops those off the stack, adds them, and pushes the result
iload_2 # Push the value from local variable 2 onto the stack
iadd # Pops those off the stack, adds them, and pushes the result
Now the top value on the stack is the result of adding together those three local variables.
Let's look at that second example in more detail:
We'll assume:
- The stack is empty to start with (which is almost never actually true, but we don't care what's on it before we start)
- Local variable 0 contains
27
- Local variable 1 contains
10
- Local variable 2 contains
5
So initially:
+−−−−−−−+ | stack | +−−−−−−−+ +−−−−−−−+
Then we do
iload_0 # Push the value from local variable 0 onto the stack
Now we have
+−−−−−−−+ | stack | +−−−−−−−+ | 27 | +−−−−−−−+
Next
iload_1 # Push the value from local variable 1 onto the stack
+−−−−−−−+ | stack | +−−−−−−−+ | 10 | | 27 | +−−−−−−−+
Now we do the addition:
iadd # Pops those off the stack, adds them, and pushes the result
It "pops" the 10
and 27
off the stack, adds them together, and pushes the result (37
). Now we have:
+−−−−−−−+ | stack | +−−−−−−−+ | 37 | +−−−−−−−+
Time for our third int
:
iload_2 # Push the value from local variable 2 onto the stack
+−−−−−−−+ | stack | +−−−−−−−+ | 5 | | 37 | +−−−−−−−+
We do our second iadd
:
iadd # Pops those off the stack, adds them, and pushes the result
That gives us:
+−−−−−−−+ | stack | +−−−−−−−+ | 42 | +−−−−−−−+
(Which is, of course, the Answer to the Ultimate Question of Life the Universe and Everything.)