Topic 104 of 119 14. Additional Java Topics Interactive Tutorial

Java Memory Management: Heap, Stack, Metaspace, and Garbage Collection

9 mins read Last updated: 22 Aug 2026 32,992 views

Direct Answer & Core Concept

Java memory management is one of the reasons Java applications can run reliably without requiring developers to manually allocate and free memory. The Java Virtual Machine (JVM) manages memory for objects, method calls, class metadata, and other runtime data.

Understanding the Java heap, stack, metaspace, and garbage collection helps developers write more efficient applications and troubleshoot problems such as OutOfMemoryError, excessive memory usage, and slow garbage collection.

In this tutorial, you will learn how Java memory is organized, where objects and local variables are stored, how garbage collection works, and how to avoid common memory-related problems.

Detailed Explanation & Mechanics

What Is Java Memory Management?

Java memory management is the process used by the JVM to allocate, organize, use, and reclaim memory while a Java program is running.

Unlike languages where developers explicitly call functions such as malloc() and free(), Java automatically manages most application memory through the JVM and its garbage collector.

A simple Java program may use several runtime memory areas:

  • Heap

  • Java stacks

  • Metaspace

  • Program Counter (PC) registers

  • Native method stacks

The heap and stack are the two areas developers encounter most often, while metaspace is especially important when working with classes, class loaders, frameworks, and dynamic class generation.


Java Memory Structure

A simplified view of JVM memory looks like this:

Main.java
Run Online
Java Virtual Machine | +----------------+----------------+ | | | Heap Stacks Metaspace | | | Objects Method calls Class metadata Arrays Local variables Runtime metadata Instance data References Method information | Garbage Collector

Each memory area has a different purpose. The heap mainly stores dynamically allocated objects, stacks hold execution-related data for individual threads, and metaspace stores class metadata.


1. Java Heap Memory

The heap is the main runtime memory area where Java objects and arrays are allocated.

Whenever you create an object using new, the object is normally allocated on the heap.

Main.java
Run Online
class User {
    String name;
    User(String name) {
        this.name = name;
    }
}public class Main {
    public static void main(String[] args) {
        User user = new User("Tanmoy");
        System.out.println(user.name);
    }
}

Here, the User object is created on the heap.

The variable user is a reference used to access that object. The exact physical representation of references and objects is JVM implementation-dependent, but conceptually it is useful to think of the object as living on the heap and the local reference as belonging to the executing method's stack frame.


What Is Stored in the Heap?

The heap primarily contains dynamically allocated objects and arrays.

For example:

Main.java
Run Online
int[] numbers = new int[5];
String name = new String("Java");
User user = new User("Alex");

The arrays and objects are allocated in heap memory.

The heap can also contain the object graphs formed when objects refer to other objects:

Main.java
Run Online
User | +---- Address | +---- Orders | +---- Order +---- Order

The garbage collector examines object reachability to determine which heap objects can eventually be reclaimed.


Heap Is Shared Between Threads

The Java heap is generally shared by all threads belonging to the JVM.

For example:

Main.java
Run Online
class Counter {
    int value = 0;
}

If multiple threads access the same Counter object, they can access the same heap object.

Main.java
Run Online
Thread 1 | | Counter | Heap | Thread 2

Because shared heap data can be accessed concurrently, applications may need synchronization or other concurrency mechanisms when multiple threads modify the same mutable objects.


Heap Generations

Many Java garbage collectors organize heap memory into regions or generations, although the exact organization depends on the collector.

A traditional generational model looks like:

Main.java
Run Online
Heap | +----------------------+ | Young Generation | | | | Eden | | Survivor | | Survivor | +----------------------+ | +----------------------+ | Old Generation | +----------------------+

New objects are commonly allocated in a young area.

Objects that remain reachable through several collection cycles may eventually be promoted to an older area.

The exact implementation differs between collectors, so developers should avoid assuming that every JVM uses exactly the same heap layout.


Young Generation

The young generation is designed around the observation that many objects become unreachable shortly after they are created.

For example:

Main.java
Run Online
for (int i = 0;
i < 100000;
i++) {
    String message = "Temporary " + i;
}

Many temporary objects created during such operations may become unreachable relatively quickly.

Garbage collection can reclaim unreachable objects so their memory can be reused.


Eden Space

In traditional generational terminology, new objects are initially allocated in the Eden area.

For example:

Main.java
Run Online
User user = new User("Alex");

The newly created object may initially be allocated in Eden.

When a young-generation collection occurs, objects that are still reachable can be moved or otherwise retained in survivor areas.


Survivor Spaces

Traditional generational collectors commonly use survivor areas to hold objects that survive young-generation collections.

A simplified model is:

Main.java
Run Online
Eden | | Collection vSurvivor | | Survives more collections vOld Generation

The exact behavior depends on the garbage collector being used.


Old Generation

Objects that remain reachable for a longer period may eventually be considered long-lived and promoted to an older region or generation.

Examples of long-lived objects might include:

Main.java
Run Online
static User currentUser;
List<User> users = new ArrayList<>();

However, simply being stored in a particular kind of variable does not by itself determine an object's generation. Garbage collector policies and object lifetime determine how objects are managed.


2. Java Stack Memory

Each Java thread has its own Java Virtual Machine stack.

The stack is mainly used for method execution. Each method invocation creates a stack frame containing information needed to execute that method.

Consider:

Main.java
Run Online
public static void main(String[] args) {
    calculate();
}static void calculate() {
    int x = 10;
    int y = 20;
    int result = x + y;
    System.out.println(result);
}

When calculate() starts, the JVM creates a stack frame for that method.

A simplified representation is:

Main.java
Run Online
Thread | +-- main() frame | +-- calculate() frame | +-- x +-- y +-- result

When calculate() finishes, its stack frame is removed.


Stack Is Per Thread

Unlike the shared heap, each Java thread has its own JVM stack.

Main.java
Run Online
Thread 1 Thread 2 | | Stack 1 Stack 2 | | Method frames Method frames

This separation helps each thread maintain its own method execution state.

However, references stored in different stack frames can point to the same object on the shared heap.


Local Variables and the Stack

Primitive local variables are commonly described as being stored in stack frames.

For example:

Main.java
Run Online
public static void test() {
    int age = 35;
    double salary = 50000.0;
}

The values of local variables are associated with the method's stack frame.

However, JVM implementations are free to optimize how values are represented internally. Therefore, "primitive variables always live on the stack" should be treated as a useful conceptual model rather than a strict implementation rule.


References and the Stack

Consider:

Main.java
Run Online
User user = new User("Alex");

Conceptually:

Main.java
Run Online
Stack | | user reference vHeap | | User object | name = "Alex"

The local variable user belongs to the method's execution frame, while the User object is allocated on the heap.

The reference itself is not the object.


Stack Example

Consider this method:

Main.java
Run Online
static int add(int a, int b) {
    int result = a + b;
    return result;
}

When add() executes, a stack frame is created for the method.

Main.java
Run Online
Stack Frame: add()a -> 10b -> 20result -> 30

When the method returns, the frame is no longer needed.


StackOverflowError

Stack memory has a limited capacity.

If a program creates too many nested method calls, the stack can become exhausted.

A common example is infinite recursion:

Main.java
Run Online
public static void recursiveMethod() {
    recursiveMethod();
}public static void main(String[] args) {
    recursiveMethod();
}

Eventually, the JVM may throw:

Main.java
Run Online
java.lang.StackOverflowError

A common cause is uncontrolled recursion.


3. Java Metaspace

Metaspace is native memory used by the JVM for class metadata.

It replaced the old PermGen (Permanent Generation) approach in Java 8.

Metaspace can contain metadata associated with loaded classes and related runtime structures.

For example:

Main.java
Run Online
class Employee {
    private int id;
    private String name;
}

When the JVM loads Employee, it needs memory for information describing that class.

That class-related metadata is associated with metaspace.


Metaspace vs Heap

Heap and metaspace serve different purposes.

AreaMain Purpose
HeapObjects and arrays
StackMethod execution and stack frames
MetaspaceClass metadata
Native memoryJVM/native runtime needs

A simple mental model is:

Main.java
Run Online
Heap -> Employee objects -> String objects -> ArraysStack -> Method frames -> Local execution dataMetaspace -> Class metadata -> Runtime class information

PermGen vs Metaspace

Before Java 8, class metadata was associated with the Permanent Generation (PermGen).

Java 8 removed PermGen and introduced Metaspace.

Main.java
Run Online
Older Java | +-- PermGenJava 8+ | +-- Metaspace

Metaspace uses native memory rather than being part of the traditional Java heap.

You can configure a maximum metaspace size using:

Main.java
Run Online
-XX:MaxMetaspaceSize=256m

For example:

Main.java
Run Online
java -XX:MaxMetaspaceSize=256m MyApplication

4. Garbage Collection in Java

Garbage Collection (GC) is the JVM mechanism that automatically identifies objects that are no longer reachable and reclaims memory associated with them.

For example:

Main.java
Run Online
public static void main(String[] args) {
    User user = new User("Alex");
    user = null;
}

After:

the previously referenced object may become unreachable, assuming no other references point to it.

It then becomes eligible for garbage collection.


What Does "Eligible for Garbage Collection" Mean?

An object is not necessarily collected immediately when it becomes unreachable.

For example:

Main.java
Run Online
User user = new User("Alex");
user = null;

The object may now be unreachable.

That means:

Main.java
Run Online
Object -> unreachable -> eligible for GC

It does not mean:

Main.java
Run Online
Object -> immediately deleted

The JVM decides when and how garbage collection occurs.


Garbage Collection and Reachability

Modern garbage collectors generally determine whether objects are reachable from GC roots.

Examples of GC roots can include:

  • Active thread stacks

  • Static references

  • JNI references

  • Other JVM-defined root references

A simplified example:

Main.java
Run Online
GC Root | vObject A | vObject B

Both objects are reachable.

If no GC root can reach an object:

Main.java
Run Online
GC RootObject A Object B | X unreachable

The unreachable object can become eligible for reclamation.


Java Garbage Collection Example

Main.java
Run Online
class Employee {
    String name;
    Employee(String name) {
        this.name = name;
    }
}public class Main {
    public static void main(String[] args) {
        Employee employee = new Employee("John");
        employee = null;
        System.out.println("Employee reference removed");
    }
}

After assigning null, the original Employee object may become unreachable.

The JVM can reclaim its memory later when the garbage collector determines that collection is appropriate.


Can We Force Garbage Collection?

Java provides:

For example:

However, this is only a request or hint to the JVM. It does not guarantee that garbage collection will happen immediately.

Therefore, application code should generally not depend on System.gc() for correctness.


Types of Garbage Collection

Java has several garbage collectors designed for different workloads and requirements.

Common collectors include:

  • Serial GC

  • Parallel GC

  • G1 GC

  • ZGC

  • Shenandoah

The appropriate collector depends on factors such as heap size, throughput requirements, latency goals, and the Java version.


Serial Garbage Collector

The Serial GC is a relatively simple collector that uses a single thread for garbage collection work.

It can be suitable for small applications and environments where simplicity matters.

Example option:


Parallel Garbage Collector

The Parallel GC uses multiple threads for garbage collection and is designed primarily around throughput.

Example:

It can be useful when maximizing overall application throughput is more important than minimizing every individual pause.


G1 Garbage Collector

G1 (Garbage-First) GC divides the heap into regions and tries to focus collection work on regions containing significant amounts of reclaimable memory.

Example:

G1 is a common general-purpose collector for modern Java applications.


Z Garbage Collector

ZGC is designed for very low pause times, including applications with large heaps.

Example:

ZGC performs much of its work concurrently with application execution.

This makes it useful for applications where latency is particularly important.


Shenandoah Garbage Collector

Shenandoah is another low-pause garbage collector designed to perform substantial GC work concurrently with application threads.

The availability and exact behavior of collectors can depend on the Java distribution and version.


Stop-The-World Pauses

Some garbage collection operations may temporarily pause application threads.

This is commonly called a Stop-The-World (STW) pause.

Conceptually:

Main.java
Run Online
Application Threads | | Running v[ GC Pause] | | Resume v Application

Modern collectors perform substantial work concurrently to reduce pause times, but some STW phases can still occur.


Minor, Major, and Full GC Terminology

You may encounter terms such as:

  • Minor GC

  • Major GC

  • Full GC

These terms are widely used in Java discussions, but their exact meaning can vary depending on the garbage collector and JVM implementation.

Therefore, when diagnosing a production application, it is better to look at the actual collector, GC logs, pause events, and memory behavior rather than relying only on generic labels.


Java Memory Lifecycle

A simplified object lifecycle looks like this:

Main.java
Run Online
Object Created | v Heap | vObject Remains Reachable | vObject Becomes Unreachable | vEligible for Garbage Collection | vMemory Reclaimed

The garbage collector determines when the memory can actually be reclaimed.


Strong References

The most common Java references are strong references.

Main.java
Run Online
User user = new User("Alex");

As long as a reachable strong reference leads to the object, the object is normally considered reachable.

If the reference is removed:

the object may become eligible for garbage collection if no other reachable references exist.


Weak References

Java also provides reference types such as WeakReference.

Example:

Main.java
Run Online
WeakReference<User> reference = new WeakReference<>(new User("Alex"));

Weak references can be useful in specialized memory-sensitive designs such as certain cache implementations.

They should not be used simply because garbage collection is desired; reference types have specific semantics and use cases.


Memory Leak in Java

Java has automatic garbage collection, but Java applications can still experience memory leaks.

A Java memory leak occurs when objects are no longer needed by the application but remain reachable, preventing the garbage collector from reclaiming them.

For example:

Main.java
Run Online
static List<Object> cache = new ArrayList<>();
public static void addData(Object data) {
    cache.add(data);
}

If the list continuously grows without removing objects, memory usage can keep increasing.

The objects are still reachable through cache, so the garbage collector cannot simply reclaim them.


Common Causes of Java Memory Leaks

Common causes include:

1. Unbounded Collections

Main.java
Run Online
List<String> logs = new ArrayList<>();
while (true) {
    logs.add("New log entry");
}

The list continues growing.

2. Static Collections

Main.java
Run Online
static Map<String, Object> cache = new HashMap<>();

A static collection can keep objects reachable for the lifetime of the class loader.

3. Unremoved Listeners

Objects registered as listeners but never unregistered can remain reachable longer than intended.

4. ThreadLocal Data

Improper use of ThreadLocal in long-lived threads can retain objects.

5. Large Caches

A cache without a sensible size or eviction policy can consume increasing amounts of heap memory.


OutOfMemoryError

If the JVM cannot allocate required memory, it may throw an OutOfMemoryError.

A common example is:

Main.java
Run Online
java.lang.OutOfMemoryError: Java heap space

This generally indicates that the JVM could not allocate an object in the Java heap.

Other memory-related errors include:

Main.java
Run Online
java.lang.OutOfMemoryError: Metaspace

and potentially other messages depending on the JVM and resource involved.


StackOverflowError vs OutOfMemoryError

These errors have different causes.

ErrorTypical Cause
StackOverflowErrorExcessive stack usage, often recursion
OutOfMemoryError: Java heap spaceHeap cannot satisfy allocation
OutOfMemoryError: MetaspaceClass metadata/metaspace exhaustion
Native-memory-related OOMJVM/native memory pressure

Example stack overflow:

Main.java
Run Online
static void test() {
    test();
}

Example heap pressure:

Main.java
Run Online
List<byte[]> data = new ArrayList<>();
while (true) {
    data.add(new byte[1024 * 1024]);
}

Heap Size Configuration

You can configure the initial and maximum heap sizes using JVM options.

For example:

Main.java
Run Online
java -Xms512m -Xmx2g MyApplication

Here:

  • -Xms512m sets the initial heap size

  • -Xmx2g sets the maximum heap size

These settings control the Java heap, not all memory used by the JVM process.


Metaspace Configuration

You can configure metaspace using:

and:

Main.java
Run Online
-XX:MaxMetaspaceSize=512m

Example:

Main.java
Run Online
java -XX:MetaspaceSize=128m \ -XX:MaxMetaspaceSize=512m \ MyApplication

The exact sizing should be based on application behavior rather than arbitrary values.


Monitoring Java Memory

Several tools can help investigate JVM memory usage.

Useful tools include:

For example:

You can first find the Java process:

Then inspect its heap information.


Heap Dump

A heap dump is a snapshot of objects and their relationships in the Java heap.

It can help investigate memory leaks and unexpected object retention.

A commonly used command is:

Main.java
Run Online
jcmd <PID> GC.heap_dump /tmp/heap.hprof

The resulting .hprof file can be analyzed using heap-analysis tools.

A heap dump can help answer questions such as:

  • Which objects consume the most memory?

  • Which objects are retaining other objects?

  • Why is a collection continuously growing?

  • Which object types have unusually high counts?


Java Memory Example

Consider this program:

Main.java
Run Online
import java.util.ArrayList;
import java.util.List;
public class MemoryDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        printNames(names);
    } static void printNames(List<String> names) {
        int count = names.size();
        System.out.println("Names: " + names);
        System.out.println("Count: " + count);
    }
}

Conceptually:

Main.java
Run Online
Heap | +-- ArrayList object | +-- Array backing storage | +-- String objectsStack | +-- main() frame | +-- names reference | +-- printNames() frame | +-- names reference +-- count

The names reference in both methods can refer to the same heap object.


Heap vs Stack vs Metaspace

The differences are important for Java developers.

FeatureHeapStackMetaspace
Main purposeObjects and arraysMethod executionClass metadata
Shared?Generally sharedPer threadJVM-level/native memory
Managed by GC?YesStack frames disappear with callsClass unloading can reclaim associated metadata
Typical problemHeap OOMStack overflowMetaspace OOM
Examplenew User()Local method frameLoaded class metadata

Java Memory Management Best Practices

Good memory management is mostly about controlling object lifetime and avoiding unnecessary retention.

1. Avoid Unbounded Collections

Do not allow lists, maps, or caches to grow forever.

2. Release Unneeded References

For long-lived objects, make sure references to objects that are no longer required do not remain unnecessarily reachable.

3. Use Bounded Caches

Caching can improve performance, but caches should generally have a sensible size or eviction strategy.

4. Avoid Creating Unnecessary Objects

For example, avoid repeatedly creating expensive temporary objects when a simpler design can reuse data safely.

5. Close Resources

Garbage collection is not a replacement for explicit resource management.

Use try-with-resources for resources such as files and streams:

Main.java
Run Online
try(var input = new java.io.FileInputStream("data.txt")) {
    // Read data
}

6. Monitor Production Memory

Track heap usage, GC pauses, allocation rates, and process-level memory rather than waiting for an OutOfMemoryError.

7. Investigate Leaks Instead of Only Increasing -Xmx

Increasing the heap can delay a memory problem but does not necessarily fix the underlying cause.


Common Java Memory Mistakes

Mistake 1: Thinking System.gc() Forces GC

This does not guarantee immediate garbage collection.


Mistake 2: Assuming null Immediately Frees Memory

This only removes that particular reference.

The object may still be reachable through another reference.


Mistake 3: Assuming Java Cannot Have Memory Leaks

Garbage collection prevents many manual-memory errors, but applications can still accidentally retain objects.


Mistake 4: Treating Heap and Stack as the Entire JVM Memory

The JVM uses several memory areas and native resources. Process memory can therefore be significantly larger than the configured Java heap.


Mistake 5: Increasing Heap Size Without Investigation

Changing:

to:

may postpone an issue while increasing memory consumption.

First investigate object retention, allocation behavior, GC activity, and other JVM memory areas.


Simple Java Memory Diagram

For beginners, this diagram provides a useful mental model:

Main.java
Run Online
JVM MEMORY | +--------------+--------------+ | | | HEAP STACK METASPACE | | | Objects Method frames Class metadata Arrays Local data Runtime metadata | | | | Garbage One stack Collection per thread

A more complete JVM picture also includes areas such as the PC register and native method stack.


Frequently Asked Questions

What is heap memory in Java?

Heap memory is the main runtime area used for dynamically allocated objects and arrays. It is shared among threads and managed by the JVM's garbage collection mechanisms.

What is stack memory in Java?

Each Java thread has its own JVM stack, which contains stack frames used during method execution. These frames hold execution-related information such as local variables and operand data.

What is metaspace in Java?

Metaspace is native memory used for class metadata. It replaced PermGen starting with Java 8.

Does Java automatically free memory?

Java's garbage collector automatically reclaims memory associated with objects that are no longer reachable. Resource management, such as closing files and database connections, still needs appropriate application-level handling.

Can Java have memory leaks?

Yes. A memory leak can happen when an application unintentionally keeps references to objects that it no longer needs.

What causes StackOverflowError?

Excessive stack usage commonly causes StackOverflowError. Infinite or extremely deep recursion is a common example.

What causes Java heap OutOfMemoryError?

It can occur when the JVM cannot allocate an object because the heap cannot provide enough usable memory. Excessive object retention, large allocations, or an undersized heap can contribute.

Is the stack faster than the heap?

Stack operations associated with method execution are generally simple and have different allocation/lifetime characteristics from heap allocation. However, saying "stack is always faster than heap" is an oversimplification because JVM implementations use optimizations and the actual performance depends on the workload.

Does calling System.gc() guarantee garbage collection?

No. System.gc() is only a request to the JVM and should not be used as a correctness mechanism.

What is the difference between heap and metaspace?

The heap primarily stores objects and arrays, while metaspace stores metadata associated with loaded classes.

Graphics and Design Ideas

Graphic 1: JVM Memory Architecture

Create a clean diagram showing:

Main.java
Run Online
JVM | +-- Heap | +-- Objects | +-- Arrays | +-- Stack | +-- Method Frames | +-- Local Variables | +-- Metaspace +-- Class Metadata

Graphic 2: Java Garbage Collection Lifecycle

Show:

Main.java
Run Online
Create Object↓Reachable↓No Longer Reachable↓Eligible for GC↓Garbage Collector↓Memory Reclaimed

Graphic 3: Heap Generational Model

Show a simplified:

Architecture Diagram
Young Generation
 ├── Eden
 ├── Survivor
 └── Survivor

Old Generation

Add a note that actual heap organization depends on the selected garbage collector.

Graphic 4: Stack vs Heap

Use two columns:

Main.java
Run Online
STACK HEAP------ ------Per thread Generally sharedMethod frames ObjectsLocal execution data ArraysShort-lived frames GC-managed objects

These visuals can make the page easier for beginners to understand and can also improve the readability of the tutorial.

Runnable Code Example: Java Memory Management

Java 17+
Main.java
Run Online
public class MemoryManagementDemo {
    public static void main(String[] args) {
        Runtime rt = Runtime.getRuntime();
        System.out.println("Available Processors: " + rt.availableProcessors());
        System.out.println("Total Heap Memory: " +(rt.totalMemory() /(1024 * 1024)) + " MB");
        System.out.println("Max Heap Memory:   " +(rt.maxMemory() /(1024 * 1024)) + " MB");
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

What is the difference between JVM Heap and Stack memory?

Stack memory holds method frames, local variables, and primitive values per thread; Heap memory holds all instantiated objects shared across threads.
Q2

What is Metaspace in Java 8+?

Metaspace replaced PermGen in Java 8 and resides in native OS memory rather than continuous JVM heap.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Use ZGC (-XX:+UseZGC) for low-latency systems requiring sub-millisecond GC pause times.