Introduction to Java’s Memory Model — Heap, Stack, and Metaspace

Alexander Obregon
4 min readMar 30, 2023

--

Image Source

Introduction

Java’s memory model plays a important role in the efficient execution of Java applications. A solid understanding of the memory model can help developers avoid performance bottlenecks and memory leaks. This article will introduce you to the three primary components of Java’s memory model: Heap, Stack, and Metaspace. We’ll cover their respective roles and provide code examples to show their usage.

Heap

The heap is a critical component of Java’s memory model, serving as the primary area for storing Java objects. Whenever an object is created using the new keyword in Java, memory for that object is allocated from the heap. This is also where the Garbage Collector operates, reclaiming memory used by objects that are no longer needed, which helps prevent memory leaks and excessive memory usage.

Structure of the Heap

The Java heap is subdivided into three main areas:

  • Young Generation: This is where all new objects are allocated. The Young Generation is further divided into one ‘Eden’ space and two ‘Survivor’ spaces. Objects initially reside in Eden and move to a Survivor space if they remain alive after a garbage collection event.
  • Old Generation: Objects that have survived several garbage collection cycles in the Young Generation are promoted to the Old Generation. It is designed for objects with a longer lifecycle. The threshold of survival is set by the garbage collector’s policies and can be tuned by the developer.
  • Permanent Generation (only in JVMs before Java 8): This part of the heap holds metadata such as classes and methods, which do not change frequently.

With Java 8 and later, the introduction of Metaspace has replaced the Permanent Generation.

Example

public class HeapExample {
public static void main(String[] args) {
Customer customer = new Customer("John Doe");
System.out.println(customer.getName());
}
}

class Customer {
private String name;

public Customer(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

In the above example, the Customer object, including its name attribute, is allocated on the heap. The heap's ability to dynamically allocate memory makes it ideal for managing complex objects with varying lifespans.

Stack

Each thread in a Java application has its own stack, which is used for storing short-lived variables and method call information. The stack is smaller in size compared to the heap but is crucial for handling method invocations and storing local variables and intermediate outcomes of expressions.

How the Stack Works

When a new method is invoked, a new block called a “stack frame” is created on the stack. This stack frame contains all the local variables, parameters, and the return address of the method. Once the method completes execution, its stack frame is discarded, making this area highly efficient in managing memory that is only needed during a method call.

Example

public class StackExample {
public static void main(String[] args) {
int result = add(5, 10);
System.out.println("Result: " + result);
}

public static int add(int a, int b) {
int sum = a + b;
return sum; // The local variables 'a', 'b', and 'sum' are stored in the stack frame for the add method.
}
}

This example demonstrates how the stack is used to manage the flow of method execution and the lifecycle of local variables.

Metaspace

Metaspace is a non-heap memory area that came into existence with Java 8, replacing the Permanent Generation. It is used to store metadata such as class definitions, method data, and field data. Unlike the heap, Metaspace is allocated out of the native memory, and its size is not fixed but can increase dynamically, which helps prevent the OutOfMemoryErrors that were possible with the Permanent Generation.

Monitoring and Managing Metaspace

Since Metaspace can grow dynamically, monitoring its size and usage is crucial to prevent system memory from being exhausted. Java provides several options for monitoring and managing the size of Metaspace, such as -XX:MetaspaceSize and -XX:MaxMetaspaceSize, which allow developers to set initial and maximum Metaspace sizes, respectively.

Importance of Metaspace

The introduction of Metaspace enhances performance and scalability by dynamically adjusting memory usage based on application demands, thereby allowing Java applications to manage class metadata more efficiently. This is particularly beneficial in environments where many classes are loaded and unloaded.

Conclusion

Understanding Java’s memory model is crucial for optimizing application performance and avoiding memory-related issues. By learning about the heap, stack, and metaspace, you can make better-informed decisions when developing Java applications. Always monitor memory usage to ensure your applications run efficiently and to detect potential memory leaks.

  1. Oracle’s Garbage Collection Basics
  2. Java Memory Model (JMM) specification
  3. Java VisualVM

--

--

Alexander Obregon

Software Engineer, fervent coder & writer. Devoted to learning & assisting others. Connect on LinkedIn: https://www.linkedin.com/in/alexander-obregon-97849b229/