Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Java Memory Management

You're reading from   Java Memory Management A comprehensive guide to garbage collection and JVM tuning

Arrow left icon
Product type Paperback
Published in Nov 2022
Publisher Packt
ISBN-13 9781801812856
Length 146 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Maaike van Putten Maaike van Putten
Author Profile Icon Maaike van Putten
Maaike van Putten
Dr. Seán Kennedy Dr. Seán Kennedy
Author Profile Icon Dr. Seán Kennedy
Dr. Seán Kennedy
Arrow right icon
View More author details
Toc

Table of Contents (10) Chapters Close

Preface 1. Chapter 1: Different Parts of the Java Memory 2. Chapter 2: Primitives and Objects in Java Memory FREE CHAPTER 3. Chapter 3: Zooming in on the Heap Space 4. Chapter 4: Freeing Memory with Garbage Collection 5. Chapter 5: Zooming in on the Metaspace 6. Chapter 6: Configuring and Monitoring the Memory Management of the JVM 7. Chapter 7: Avoiding Memory Leaks 8. Index 9. Other Books You May Enjoy

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

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

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.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image