Course contents

Generational collection

Why HotSpot splits the heap by object age: the weak generational hypothesis, Eden and survivor spaces, aging and the tenuring threshold, premature promotion, card tables, minor vs major vs full GC, TLAB allocation, G1 regions and humongous objects, and Generational ZGC in Java 21.

PremiumMust know for interviewsLesson19 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: a mailroom with two trays
  2. Under the hood: three minor GCs, step by step
  3. Real world: a full old generation is usually a symptom
  4. Advanced: tenuring threshold and premature promotion
  5. Card tables: finding old-to-young pointers
  6. Minor, major, mixed and full GC
  7. Allocation: TLABs and the pointer bump
  8. G1: the same generations, on regions
  9. Generational ZGC in Java 21
  10. Useful flags and reading one log line
  11. How to say it in the interview

🐣 What is generational collection?

Generational collection splits the heap by object age so the JVM can clean the area full of new objects often and cheaply, and the area of long-lived objects rarely. A request handler that allocates 40 KB of builders, DTOs and strings per call will see nearly all of it become garbage before the response is sent. A collector that only looks at that young area can reclaim hundreds of megabytes in a few milliseconds, because it only has to copy the handful of objects that are still alive.

For this lesson, we'll walk an object's whole life from Eden to the old generation, then cover premature promotion, card tables, TLABs, and how G1 and Generational ZGC reshape the idea. Interviewers ask "explain Eden and survivor spaces" constantly, and strong answers explain why the design works, not only the box names.

Below the line (out of scope)

The 30-second answer

Most objects die young (the weak generational hypothesis), so HotSpot splits the heap into a young generation (Eden plus two survivor spaces, S0 and S1) and an old generation. New objects are allocated in Eden, usually through a thread-local buffer with a pointer bump. When Eden fills, a minor GC copies the live objects from Eden and the active survivor space into the empty survivor space, increments their age, and frees everything left behind in one step. Objects that reach the tenuring threshold (at most 15, adapted at runtime) or don't fit in a survivor space are promoted to the old generation, which is collected far less often by a major or full GC. A card table records old-to-young pointers so a minor GC never scans the whole old generation. Minor GCs are stop-the-world in Serial, Parallel and G1. G1 applies the same model to regions, and Java 21 adds Generational ZGC.

What you’ll learn

  • Explain the weak generational hypothesis and why it makes young collections cheap
  • Walk an object through Eden, S0, S1 and the old generation across several minor GCs
  • Describe the tenuring threshold and diagnose premature promotion from a GC log
  • Explain how card tables and remembered sets avoid scanning the old generation on every minor GC
  • Distinguish minor, major, mixed and full GC, and explain TLAB bump-pointer allocation
  • Describe G1 regions and humongous objects, Generational ZGC in Java 21, and read a one-line -Xlog:gc entry

Interview radar

Asked very often: Walk me through Eden, S0 and S1. Why are there two survivor spaces? Core follow-up: What decides when an object is promoted, and what is premature promotion? Production angle: Old generation keeps growing and full GCs appear every few minutes. Leak or tuning problem?

01Picture it first: a mailroom with two trays

Think of a busy office mailroom. All new post lands on the arrivals desk. Every hour a clerk throws away anything nobody wants, moves the letters someone still needs into one of two trays, and stamps each letter with how many hours it has survived. Next hour, the clerk empties the desk and the full tray into the other tray, stamping again. A letter with enough stamps is filed in the archive cabinet, which gets a slow, thorough clean-out only when it's nearly full.

The desk is Eden, the two trays are the survivor spaces, the stamp is the object's age, and the cabinet is the old generation. Here's the layout HotSpot gives the Serial and Parallel collectors, with the real sizes I measured for a 64 MB heap with an 8 MB young generation.

Fig. 1

The Java heap split into a young generation of Eden (6.5 MB) and two 768 KB survivor spaces, one always empty, next to a 56 MB old generation, with threads allocating into private TLAB chunks of Eden

The Java heap split into a young generation of Eden (6.5 MB) and two 768 KB survivor spaces, one always empty, next to a 56 MB old generation, with threads allocating into private TLAB chunks of Eden

Notice three things in the picture:

  • One survivor space is always empty. It's the destination for the next minor GC. That's why there are two: copying needs an empty target, and the roles swap after every collection.
  • Eden is much bigger than a survivor space. -XX:SurvivorRatio=8 makes Eden 8 times one survivor space, so each survivor is 1/10 of the young generation.
  • The old generation is the biggest area. -XX:NewRatio=2 makes old twice the size of young, so young gets a third of the heap by default (on Serial and Parallel; G1 sizes young dynamically).

Metaspace, where class metadata lives, is native memory outside this picture.

02Under the hood: three minor GCs, step by step

Let's follow objects from a food-delivery service through three minor collections. For the walk-through, I set the tenuring threshold to 2 so we can see a promotion quickly; the real default maximum is 15.

Try it yourself first

Before reading on: orderLog survives every collection. With a threshold of 2, in which collection does it reach the old generation, and which survivor space is empty after each GC?

Fig. 2

Four snapshots of the heap: before GC 1 Eden holds five objects, two of them dead; before GC 2 three survivors sit in S0 at age 1 next to new objects in Eden; before GC 3 survivors sit in S1 at ages 1 and 2; after GC 3 orderLog has been promoted to the old generation and the rest sit in S0

Four snapshots of the heap: before GC 1 Eden holds five objects, two of them dead; before GC 2 three survivors sit in S0 at age 1 next to new objects in Eden; before GC 3 survivors sit in S1 at ages 1 and 2; after GC 3 orderLog has been promoted to the old generation and the rest sit in S0

Minor GC 1. Eden holds orderLog, otp, menuPage, riderPing and cartView. Only orderLog, menuPage and cartView are reachable. The collector copies those three into S0 and sets their age to 1. otp and riderPing are never touched: Eden is reset as a whole, so dead objects cost nothing.

Minor GC 2. Meanwhile invoice and geoTick were allocated in Eden, because every new object starts there. Now menuPage and geoTick are unreachable. The collector copies live objects from Eden and S0 into the empty S1: orderLog and cartView become age 2, invoice becomes age 1. Eden and S0 are now both empty.

Minor GC 3. receipt and searchHit arrive in Eden. cartView and searchHit die. orderLog is at age 2, which has reached the threshold, so it's promoted: copied into the old generation instead of a survivor space. invoice moves to S0 at age 2 and receipt lands in S0 at age 1. S1 is empty again.

The rule behind every step: copy what's live, age it, promote it if it's old enough, then treat Eden and the source survivor space as empty. The cost of a minor GC follows the number of surviving objects, not the size of Eden.

Now let's watch real ages. This program keeps 200 small "session" arrays alive while churning 400,000 short-lived request buffers.

import java.util.ArrayList;
import java.util.List;

public class SessionChurn {
    public static void main(String[] args) {
        List<byte[]> sessions = new ArrayList<>();      // long-lived: survives every GC
        long bytes = 0;
        for (int i = 0; i < 400_000; i++) {
            byte[] requestBuffer = new byte[256];       // short-lived: dead after this iteration
            bytes += requestBuffer.length;
            if (i % 2_000 == 0) {
                sessions.add(new byte[4 * 1024]);       // 200 sessions of 4 KB each
            }
        }
        System.out.println(sessions.size() + " sessions, " + bytes + " request bytes");
    }
}

Running it on JDK 21 with Serial, an 8 MB young generation and age logging (trimmed):

$ java -XX:+UseSerialGC -Xmn8m -Xmx64m -Xlog:gc,gc+age=trace SessionChurn
GC(0) Desired survivor size 393216 bytes, new threshold 15 (max threshold 15)
GC(0) - age   1:     118912 bytes,     118912 total
GC(0) Pause Young (Allocation Failure) 7M->1M(63M) 0.364ms
GC(1) - age   1:      49496 bytes,      49496 total
GC(1) - age   2:     118832 bytes,     168328 total
GC(1) Pause Young (Allocation Failure) 7M->1M(63M) 0.378ms
...
GC(5) - age   6:     118832 bytes,     374304 total
GC(6) Desired survivor size 393216 bytes, new threshold 7 (max threshold 15)
GC(6) - age   7:     118832 bytes,     423648 total
GC(6) Pause Young (Allocation Failure) 7M->1M(63M) 0.158ms

Notice four things:

  • Each pause turned about 7 MB into 1 MB in under 0.4 ms. About 6 MB of request buffers died, and the collector never visited them.
  • The 118 KB cohort that was alive at GC(0) shows up one age older in every log: age 1, age 2, and on up to age 7. That's the survivor ping-pong from the diagram.
  • The threshold isn't fixed. At GC(6) the survivors totalled 423,648 bytes, more than the desired 393,216 (half of a survivor space, -XX:TargetSurvivorRatio=50), so the JVM lowered the threshold from 15 to 7. Objects aged 7 get promoted at the next GC.
  • 15 is a hard ceiling: HotSpot keeps the age in 4 bits of the object header.

03Real world: a full old generation is usually a symptom

Premium lesson

Keep reading with the Java Pass

The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.

  • Advanced: tenuring threshold and premature promotion
  • Card tables: finding old-to-young pointers
  • Minor, major, mixed and full GC
  • Allocation: TLABs and the pointer bump
  • G1: the same generations, on regions
  • Generational ZGC in Java 21
  • Useful flags and reading one log line
  • How to say it in the interview

From ₹299 · one-time payment · No auto-renewal

Interview questions for this topicComing soon

Further reading (11)