avatar
Java Internals

JVM Heap Memory Explained: Young, Old, and Permanent Generation

Sarvar Musazade7 min read—views

Every Java object you create has to live somewhere.

java
User user = new User("Sarvar");

It is one line of code.

But behind it, the JVM makes several decisions:

Where should this object live, and when should it be removed?

To answer that, we need to understand two memory areas: the stack and the heap.

Let's start from the beginning.

1. What Is the Heap?

The heap is the memory area where the JVM stores objects.

Every time you write new, the object is created on the heap.

User user = new User("Sarvar");
List<String> names = new ArrayList<>();

Both the User object and the ArrayList object live on the heap.

The heap has three important properties:

2. What Is the Stack?

The stack is a small memory area that belongs to a single thread.

Every time a method is called, the JVM creates a stack frame for it.

That frame stores:

When the method finishes, its frame is removed immediately.

java
public void createUser() {    int age = 25;                     // stack    User user = new User("Sarvar");   // reference -> stack                                      // User object -> heap}


Notice something important:

The variable user is on the stack. The object it points to is on the heap.

3. Stack vs Heap: What Is the Difference?

The Stack

The Heap

A simple way to remember it:

The stack remembers what you are doing. The heap remembers what you created.

4. Why Do We Need the Heap?

You might ask:

"If the stack is so fast, why not store everything there?"

Because stack memory disappears when a method returns.

But many objects need to live longer than the method that created them.

java
public User findUser(long id) {    User user = new User(id);    return user;   // the object must survive after this method ends}

Objects are also often shared between threads, for example a cache, a connection pool, or a Spring bean.

And the size of many objects is not known in advance. A list can grow from 10 items to 10 million.

The stack cannot handle any of these cases.

The heap can.

5. Why Is the Heap Divided Into Generations?

Cleaning the entire heap every time would be slow.

So the JVM designers used a simple observation, known as the weak generational hypothesis:

Most objects die young.

Think about it:

java
for (Order order : orders) {    String message = "Processing " + order.getId();    log.info(message);}

Each message string is useless after one loop iteration.

But objects like a cache or a Spring bean live for the entire application lifetime.

So the heap is divided by object age:

6. Young Generation

Every new object starts here.

The Young Generation has three parts:

The process works like this:

  1. New objects are created in Eden.
  2. When Eden is full, a Minor GC runs.
  3. Dead objects are removed.
  4. Living objects are moved to a Survivor space, and their age increases by 1.
  5. On each Minor GC, surviving objects move between S0 and S1.

Minor GC is usually fast, because most objects in Eden are already dead.

7. Old Generation

When an object survives enough Minor GCs, it is promoted to the Old Generation.

The threshold is controlled by:

-XX:MaxTenuringThreshold (maximum 15)

Objects that usually end up here:

The Old Generation is cleaned by a Major GC (or a Full GC when the whole heap is collected).

These collections are slower, and depending on the collector, they can cause longer pause times in your application.

That's why too many objects being promoted too early can hurt performance.

8. Permanent Generation (PermGen) and Metaspace

Before Java 8, the JVM had a special area called PermGen.

It stored:

The problem: PermGen had a fixed maximum size.

Applications that loaded many classes, such as application servers with frequent redeployments, often failed with:

java.lang.OutOfMemoryError: PermGen space

In Java 8, PermGen was removed and replaced by Metaspace.

PermGen (Java 7 and earlier)

Metaspace (Java 8+)

Location

JVM-managed memory

Native memory

Size

Fixed maximum (-XX:MaxPermSize)

Grows by default (-XX:MaxMetaspaceSize)

Typical error

OutOfMemoryError: PermGen space

OutOfMemoryError: Metaspace

9. The Full Picture: An Object's Life

Let's follow one object from creation to removal:

  1. new User() is created in Eden.
  2. Eden fills up, a Minor GC runs, the object survives and moves to S0.
  3. Next Minor GC, it survives again and moves to S1.
  4. This repeats until the age threshold is reached.
  5. The object is promoted to the Old Generation.
  6. When no references remain, it is removed during a Major GC.

Meanwhile, the class User itself is described in Metaspace.

10. Useful JVM Flags

-Xms512m # initial heap size
-Xmx2g # maximum heap size
-Xmn256m # young generation size
-XX:SurvivorRatio=8 # Eden to Survivor ratio
-XX:MaxTenuringThreshold=15 # promotion age
-XX:MaxMetaspaceSize=256m # Metaspace limit
-Xss1m # stack size per thread

A note for modern Java: G1, the default collector since Java 9, divides the heap into many regions instead of large fixed blocks. But the idea of Young and Old generations still applies.

Final Takeaway

The stack stores method calls, local variables, and references. It is small, fast, and belongs to one thread.

The heap stores objects. It is large, shared, and cleaned by the Garbage Collector.

The heap is divided into generations because:

Most objects die young.

Once you understand this, errors like OutOfMemoryError and long GC pauses stop being mysterious.

They become problems you can reason about.

Follow My Content

If you enjoy content about Java, backend engineering, concurrency, computer architecture, and system design, you can follow my work on:

Comments (0)

0/1000

Loading comments...