Every hardware architecture defines its own instruction set. Instructions, in the simplest sense, are commands to the processor unit to trigger a specific electronic operation on a definite number of operands - which again are memory locations or physical registers. Probably, the creators of Java were inspired by this functioning of physical machines. When they conceptualized a virtual version of the metallic machines (hardware) and called it the Java Virtual Machine, a Virtual Machine Instruction Set also came into existance. They named these Virtual Machine Instructions as Bytecodes.
We do not, however, have virtual registers! The execution is carried out on a stack-machine. The Bytecodes were hence designed to operate on an operand stack.
Consider two simple Java methods written to find the area of a circle.
public double areaOfACircle ( double radius )
{
double PI = 3.14;
double area = PI * getSquare ( radius ); //don't ask me why its not PI*radius*radius
return area;
}
private double getSquare ( double d )
{
return d * d;
}
On compilation using "javac", the bytecodes of these methods are as follows:
public double areaOfACircle(double);
Code:
0: ldc2_w #7; //double 3.14d
3: dstore_3
4: dload_3
5: aload_0
6: dload_1
7: invokevirtual #9; //Method getSquare:(D)D
10: dmul
11: dstore 5
13: dload 5
15: dreturn
private double getSquare(double);
Code:
0: dload_1
1: dload_1
2: dmul
3: dreturn
Just like a uni-processor, the Java Virtual Machine executes these bytecodes one at a time. It is very easy to walk these sequences and get a feeling of how interpretation
happens in the stack-based interpreter. Let us begin with an invocation to areaOfACircle ( ). We start with an empty operand stack. Each method is also supplied a
local variable array which holds the locals defined by the method and the arguments passed to this method.
Here are local variable array is going to look like this:
Slot 0 : Argument 0. Reference to the this object
Slot 1: Higher word of argument 1 - double radius
Slot 2: Lower word of argument 1 - double radius
Slot 3: Higher word of double PI
Slot 4: Lower word of double PI
Slot 5: Higer word of double area
Slot 6: Lower word of double area
Note : Only double and long locals take twp local variable slots. Others take one.
Instruction Semantics State of the operand stack after execution
-------------------------- ------------------------------------------------------------------------------------- ---------------------------------------------------------
0:ldc2_w #7; //double 3.14d #Push the double constant onto the stack -> 3.14
dstore_3 #Store the top of stack in local variable #3(type=double, name=PI) (empty stack)
dload_3 #Load local variable #3(type=double) onto the stack -> PI
aload_0 #Load local variable #0( 1st argument,this object) onto the stack -> this, PI
dload_1 #Load local variable #1( 2nd argument, double radius) onto stack -> radius, this, PI
invokevirtual #9; //Method getSquare:(D)D #Invoke the method getSquare on "this" object, pass radius as arg
# The return value is pushed back onto the stack -> getSquare(radius), PI
dmul #Multiply the first two operands on the stack and push the result -> PI*getSquare(radius)
#dstore 5 #Store the stack top to local variable#5(type-double, name-area) (empty stack)
#dload 5 #Load the local variable #5 onto the stack -> area
return #return stack top to the caller 's stack (empty stack)
The execution of getSquare ( ) happens in the same fashion. When it does a dreturn, the return value is pushed onto the caller's operand stack. One observation which the
curious reader would make is that the VM Instructions are typed - i.e Addition of integers is done using iadd, whereas addition of doubles is done using dadd.
This specification from Oracle-Sun describes the nature and semantics of all the VM Instructions in detail. Please go through. Thank you.