The Ultimate Stack Frame
This blog is an extension of the previous blog where I have talked about how primitive types are stored on a stack.
Here is the link of my previous post
In this blog, we will discuss on Stack Frames. So, let us get started…
As we all know, for every new thread, a separate stack is created and for every method execution by that newly created thread, a new stack frame is pushed onto the stack.
Each stack frame consists of method parameters, local variables, intermediate operations, etc.
Whenever JVM invokes a method, it first checks the class data to determine the size of the local variable array. Once the size is identified, a local variable array is created and all the method parameters, local variables are stored in it. The storing of data into the local variable array is performed using JVM instructions such as istore, astore, etc.
Once the method execution gets over, the corresponding stack frame is popped out of the stack.
As soon as the thread completes the execution of all the methods, the corresponding stack becomes empty, and then it is destroyed by the JVM.
The data present in the stack is available only to the corresponding thread.
Let us now consolidate our knowledge by making a deep dive into the stack frame.
A stack frame is divided into 3 parts
- Local Variable Array
- Operand Stack
- Frame Data (It will be discussed in a separate blog)
Local Variable Array
- It is a zero-based array (index starts from 0)
- The array is first occupied by method parameters followed by local variables (ordered fashion).
- If a method is an instance method, the first slot in a local variable array is occupied by “this” reference.
- Each slot is of 32 bits.
- Values of int, float, and references occupy a slot whereas long and double occupies two slots.
- In the case of byte, short, boolean and char, values are expanded to occupy a full-length slot (32 bits).
Let us analyze the elements of a local variable array with the help of an example.
If you see the above example, there are two methods — one is the instance method and the other one is the static method. But why two methods?
To find the answer to this question, let us analyze the byte code of the static method.
If you see the highlighted byte codes, there are few “load ”instructions. Let us go through them one by one.
The first highlighted instruction is iload_0 which stands for loading an integer from array index 0. The next instruction, lload_1 is for long which loads from array index 1. But as we know, long is of size 64 bits, so it occupies array index 1 and index 2. Moving onto the next instruction, fload_3 loading the value from array index 3, dload 4 from array index 4, and 5 as the size is 64 bits. The next one is aload 6 where “a” indicates references. In this case, it holds the reference of java.lang.Object and as we know, references are of size 32 bits, so it occupies array index 6. The next instruction is iload 7 for method parameter byte. In this case, the value is loaded as an integer value. The next instruction is iload 8, which is the byte code of the local variable (int z = 10), and it occupies the array index 8. This is how method parameters and local variables are stored onto the local variable array.
So now we move on to the byte code of the non-static method.
The only difference between static and non-static methods is that the non-static method has access to “this” reference, which points to the current object. The reference “this” (aload_0) is created when the constructor is called. In the case of the non-static method, the 1st array index of the local variable array is occupied by the “this” reference and it has the size of 32 bits.
Operand Stack
- This stack is used as a workspace area.
- Since it is a stack, it can either perform push operation or pop operation.
All JVM “load” instructions are used to load the data from the local variable array to the operand stack which acts as working space for the JVM.
Do you know that stack throws StackOverFlowError?
This error can pop up if you have a method being called in an infinite loop and for each method call, a stack frame getting pushed onto the stack, eventually stack getting exhausted and there is no way to free up the memory. TRY IT YOURSELF!!!
One of the best ways to write code is to have small methods where each method is designated to perform a certain role or a task and avoid making unnecessary variables as they will occupy memory.
Frame Data
In addition to the local variables and operand stack, the Java stack frame includes data to support constant pool resolution, normal method return, and exception dispatch. This data is stored in the frame data portion of the Java stack frame.
Whenever the Java virtual machine encounters any of the instructions that refer to an entry in the constant pool, it uses the frame data’s pointer to the constant pool to access the information.
My next blog will be on Wrapper classes where we will discuss the difference between the primitives and the wrapper classes. Aside from constant pool resolution, the frame data must assist the virtual machine in processing a normal or abrupt method completion. If a method completes normally, the virtual machine must restore the stack frame of the invoking method. It must set the pc register to point to the instruction in the invoking method that follows the instruction that invoked the completing method. If the completing method returns a value, the virtual machine must push the value onto the operand stack of the invoking method.
The frame data must also contain some kind of reference to the method’s exception table, which the virtual machine uses to process any exceptions thrown during the course of execution of the method. An exception table defines ranges within the bytecodes of the method that are protected by the catch clauses. Each entry in the exception table gives a starting and ending position of the range protected by the catch clause, an index into the constant pool that gives the exception class being caught, and a starting position of the catch clause’s code.
When a method throws an exception, the Java virtual machine uses the exception table referred to by the frame data to determine how to handle the exception. If a virtual machine finds a matching clause in the method’s exception table, it transfers control to the beginning of that catch clause. If the virtual machine doesn’t find a matching catch clause, the method completes abruptly. The virtual machines uses the information in the frame data to restore the invoking method’s frame. It then rethrows the same exception in the context of the invoking method.
In addition to the above information, the frame data also stores information to support debugging.
That’s all from this post. I hope you guys liked it.
Thank You.