Storing variables on the stack
Variables used in a method are stored on the stack. The stack memory is the memory that is used for executing methods. In Figure 1.7, we have shown a stack area for three threads, each containing several frames.
Figure 1.7 – Overview of the frames in the stack area for three threads
Inside a method, primitives and references exist. Every thread in the application has its own stack. The stack consists of frames. Every method that gets invoked comes with a new frame on the stack. When the method execution is finished, the frame is removed.
If the stack memory is too small to store what is needed for the frame, StackOverFlowError
is thrown. When there is not enough space for a new stack for a new thread, OutOfMemoryError
is thrown. The method that currently is being executed by a thread is called the current method and its data is held in the current frame.
Current frame and current method
The reason that a stack is named as such is that it can only access the top frame of the stack. You can compare this to a stack of plates where you can only (safely) take plates from the top. The top frame is called the current frame as it belongs to the current method – the method that is being executed at that time.
If a method that is being executed calls another method, a frame gets placed on top of the frame. This new frame becomes the current frame since the newly invoked method is the current method that is being executed.
In Figure 1.7, there are three current frames, because there are three threads. The current frames are the ones on top. So, let’s see the following:
- The Frame for method y is for thread 1
- The Frame for method c is for thread 2
- The Frame for method k is for thread 3
When the method is executed, it gets removed. The previous frame then becomes the current frame again since the method that has called the other method is the one that gets the control back for the moment and is the method that is being executed at that time (the current method).
Elements of the frame
A frame contains a number of elements. These elements are needed to store all the necessary data for a method to be executed. An overview of all the elements can be seen in Figure 1.8:
Figure 1.8 – Schematic overview of a stack frame
As you can see, a frame has a local variable array, an operand stack, and frame data. Let us explore the separate elements of the frame in more detail.
The array of local variables
The local variables of the frame are stored in an array. This array length is set during compile time. The array has single and double spots. Single spots are for types of int
, short
, char
, float
, byte
, boolean
, and reference
. The double spots are for long
and double
(they are 64 bits in size).
The local variables can be accessed by their index. There are two types of methods: static
methods (class methods) and instance
methods. For these instance
methods, the first element of the local variable array is always the reference to the object that they exist on, also known as this
. Parameters that are given to the method start from index 1
on the local variable array.
For static
methods, no instance needs to be provided to the frame, so they start with the parameters that were used to invoke them at index 0
.
The operand stack
This concept can be a bit rough, so bear with me. Every stack frame has an operand stack – a stack (operand stack) on the element (frame) of the stack – and this operand stack is used to write operands so that they can be, well, operated upon. This is where all the values fly around.
It begs for an example, so let’s have a look at one. When the frame is newly created, there is nothing on the operand stack, but let’s assume that the method for which the frame was created is going to do a basic mathematical operation, such as adding x and y.
x
and y
are local variables and their values are in the aforementioned array of local variables. In order to do the operation, their values need to be pushed to the operand stack – so, the value of x
is going to be pushed first and the value of y
is going to be pushed second.
The operand stack is a stack, so when it needs to access the variables, it can only grab them from the top of the stack. It pops y
first and then pops x
. After this, the operand stack is empty again. The operation that is being performed knows the order of the popped variables. When the operation is done, the result is pushed to the operand stack and it can be popped from there.
The operand stack is also used for other important operations, such as preparing parameters that need to be sent to a method as input and receiving the results that the methods return.
Frame data
The frame data consists of diverse data needed to execute the method. Some examples are a reference to the constant pool, how to normally return a method, and abruptly completed methods (or exceptions).
The first of these, a reference to the constant pool, requires special attention. A class file has all the symbolic references that need to be resolved in the runtime constant pool. This pool contains all the constants needed for running the class and it is generated by the compiler. It contains the names of identifiers in the class and the JVM uses this file during runtime to link the class to other classes.
Every frame has a reference to the constant pool of the current method at runtime. Since this is a runtime constant pool with symbolic references, linking needs to happen dynamically.
Let’s have a look at what the constant pool looks like for our silly Example
class. Here is the code for our Example
class:
package chapter1;
public class Example {
public static void main(String[] args) {
int number = 3;
char letter = 'z';
double percentage;
percentage = 8.6;
}
}
By running the following command (after we compiled it with javac Example.java
), we can see the constant pool:
javap -v Example.class
Here, you can see the output:
Classfile /Users/maaikevanputten/Documents/packt/memorymanagement/src/main/java/chapter1/Example.class
Last modified 12 Jun 2022; size 298 bytes
SHA-256 checksum b2a6321e598c50c5d97ba053ca0faf689197df18c5141b727603 eaec0fecac3e
Compiled from "Example.java"
public class chapter1.Example
minor version: 0
major version: 61
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #9 // chapter1/Example
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Double 8.6d
#9 = Class #10 // chapter1/Example
#10 = Utf8 chapter1/Example
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 SourceFile
#16 = Utf8 Example.java
{
public chapter1.Example();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: iconst_3
1: istore_1
2: bipush 122
4: istore_2
5: ldc2_w #7 // double 8.6d
8: dstore_3
9: return
LineNumberTable:
line 5: 0
line 6: 2
line 8: 5
line 9: 9
}
SourceFile: "Example.java"
As you can see, the constant pool has 16 entries. These are the ones created by us, but also some created by Java. They are needed to execute the program, so the name of the program, method, and so on are created in the constant pool to run the program.
Values on the stack
The values of the primitive’s local variables are stored directly in the stack – to be more precise, on the array of the frame of the method that the local variables are in. Objects are not stored on the stack. Instead, the object reference is stored on the stack. The object reference is the address at which to find the object on the heap.
Primitives and wrapper classes
Be careful not to confuse the primitive types with their object wrapper classes. They are easily recognized by the type being in uppercase. The wrapper class objects don’t live on the stack, simply because they are objects. Whenever a method has been executed, the values of the associated primitives are cleaned up from the stack and they are gone forever.
Some wrapper classes are more easily recognized than others. Let’s take a look at a code snippet:
int primitiveInt = 2;
Integer wrapperInt = 2;
char primitiveChar = 'A';
Character wrapperChar = 'A';
As you can see, the wrappers start with a capital and are longer. For many types, however, the word is exactly the same and the only difference is that it starts with a capital. Personally, I’m most often fooled by Boolean
and boolean
(I blame C# for this since the equivalent of the Java boolean
primitive in C# is bool
).
Here, you can see the difference between the other primitives and their reference types:
short primitiveShort = 15;
Short wrapperShort = 15;
long primitiveLong = 8L;
Long wrapperLong = 8L;
double primitiveDouble = 3.4;
Double wrapperDouble = 3.4;
float primitiveFloat = 5.6f;
Float wrapperFloat = 5.6f;
boolean primitiveBoolean = true;
Boolean wrapperBoolean = true;
byte primitiveByte = 0;
Byte wrapperByte = 0;
Please note that they have the exact same name. We need to look at the first letter to distinguish between the wrapper class and the primitive type. Wrapper classes are objects and these are created differently. Let’s find out how.