You are almost there, there is only one missing link in your understanding.
Local variables (or references to an object stored in a local variable, if we are talking about non-primitive types) are actually stored in the local variable table, and not in the operand stack. They only get inserted into the stack when they are used by the call.
(what is confusing is that the local variable table itself is also stored on the stack, but it is a separate stack from which the bytecode for the operands is used. From the point of view of the bytecode, this is a real table with a fixed size and freely indexable. )
You can use javap to see which bytecode is generated from your code. What you will see is something like this:
public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=3, args_size=1 0: new #2
First of all, what is this line?
stack=3, locals=3, args_size=1
This is the metadata that tells the JVM that this method has a operand stack of not more than 3 entries, 3 local variables and takes 1 argument. But, of course, this is wrong, our method takes no arguments and obviously has only two local variables!
The answer to this question is that non-static methods always have a "0th argument": this . This explains the number of arguments and leads us to the following important discovery: the method arguments are also stored in the local variable table . Thus, our table will have records 0,1,2, with 0 containing this at the beginning and 1 and 2 uninitialized.
With that in mind, take a look at the code! First select lines 0-7 :
- The operation code
new creates a new instance of Bar and stores the link on the stack. dup creates a copy of the same help at the top of the stack (so you now have two copies)invokespecial #3 calls the constructor of Bar and consumes the top of the stack. (now we have only one copy left)astore_1 stores the remaining link in the local variable number 1 ( 0 for this in this case)
This is what Object a = new Bar(); was compiled Object a = new Bar(); . Then you get the same for Object b = new Foo(); (lines 8-15 ).
And then an interesting bit appears, from line 16 :
getstatic #6 pushes the value of System.out the stackaload_1 pushes local variable number 1 ( a ) on the stack.invokevirtual #7 consumes both entries by calling println() on System.out with a as an input parameter.
If you want to delve deeper into it or just want to point out my mistakes, the official link to all of the above is here .