What are generations in GC
The Java Virtual Machine partitions memory into Generations based on the expected longevity of objects. Rather than scanning the entire multi-gigabyte heap on every collection p...
🟢 Junior Level
The Java Virtual Machine partitions memory into Generations based on the expected longevity of objects. Rather than scanning the entire multi-gigabyte heap on every collection pass, the JVM applies tailored, highly efficient collection algorithms to different regions depending on how long objects have survived.
Real-World Analogy
Consider an enterprise office mail and records system:
- Young Generation (Active Work Desks): Incoming daily correspondence, scratch notes, and temporary meeting agendas. Over 90% are shredded and discarded within hours. A quick recycling sweep happens multiple times a day.
- Old Generation (The Long-Term Archive): Corporate charters, tax records, and foundational contracts that have survived multiple operational quarters. Kept for years in organized storage and audited infrequently.
- Metaspace (The Building Architecture): Blueprints and building codes stored outside the main office inventory in the municipal registry (Native OS Memory).
Generational Memory Layout
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ JVM PROCESS MEMORY │
├───────────────────────────────────────────────────────────────┬────────────────────────┤
│ JAVA HEAP │ NATIVE OS MEMORY │
├─────────────────────────────────────┬─────────────────────────┼────────────────────────┤
│ Young Generation │ Old Generation │ Metaspace │
│ ┌───────────────┬────────┬───────┐ │ ┌────────────────────┐ │ ┌───────────────────┐ │
│ │ Eden │ S0 │ S1 │ │ │ Tenured │ │ │ Class Metadata │ │
│ │ (Allocations) │ (From) │ (To) │ │ │ (Long-Lived State) │ │ │ Constant Pools │ │
│ └───────────────┴────────┴───────┘ │ └────────────────────┘ │ │ Method Bytecodes │ │
└─────────────────────────────────────┴─────────────────────────┴──┴───────────────────┘
Generational Breakdown & Default Ratios
- Young Generation:
- Eden Space: Where virtually all new objects are initially allocated via thread-local allocation buffers (TLABs).
- Survivor Spaces (S0 and S1): Two identically sized staging areas (From-space and To-space) that filter out short-lived objects via ping-pong copying.
- Default Sizing: Controlled by
-XX:NewRatio=2(Old Gen is 2x Young Gen; Young is 1/3 of the heap, Old is 2/3) and-XX:SurvivorRatio=8(Eden is 8x the size of one Survivor space: Eden 80%, S0 10%, S1 10%).
- Old Generation (Tenured Space):
- Houses long-lived objects that survived multiple Minor GCs, large cached entries, and application singletons.
- Collected via Major GC or concurrent background marking.
- Metaspace (Off-Heap):
- Stores loaded class metadata, runtime constant pools, and method bytecodes in native OS memory (not subject to Java Heap
-Xmxlimits).
- Stores loaded class metadata, runtime constant pools, and method bytecodes in native OS memory (not subject to Java Heap
🟡 Middle Level
The Weak Generational Hypothesis
The entire architectural justification for generational memory rests upon the Weak Generational Hypothesis, an empirical rule confirmed across decades of production telemetry:
\[\text{Infant Mortality Rate} \approx 90\% - 98\%\]- Most allocated objects die almost immediately: Intermediate string concatenations, DTOs, method parameters, stream pipeline nodes, and lambda captures survive for mere microseconds.
- Objects that survive multiple collection cycles tend to remain alive for a very long time: Spring application context beans, database connection pools, long-term caches, and session registries.
Dual-Survivor Ping-Pong Evacuation Mechanics
A common interview question is: “Why are there two Survivor spaces (S0 and S1) instead of just one?”
The JVM uses two Survivor spaces to eliminate memory fragmentation without paying the steep CPU cost of sliding memory compaction:
Cycle 1: Minor GC
Eden: [Live A][Dead][Live B] ──> Evacuates Live A & B to S0 (Empty)
S0 (To): [Live A (Age 1)][Live B (Age 1)]
S1 (From): [ Empty ]
Result: Eden is cleared instantly with zero fragmentation!
Cycle 2: Minor GC
Eden: [Live C][Dead]
S0 (From): [Live A][Dead B] ──> Evacuates Live A & C to S1 (Empty)
S1 (To): [Live A (Age 2)][Live C (Age 1)]
S0: Cleared completely!
- At any given moment, one Survivor space is strictly empty (
To-space), while the other holds survivors from prior collections (From-space). - When Eden fills, a Minor GC executes:
- Live objects from Eden and the active
From-spaceare evacuated into the cleanTo-space. - Surviving objects have their age counter incremented by 1.
- Eden and the old
From-spaceare reset in a single hardware pointer reset. - The roles of S0 and S1 swap.
- Live objects from Eden and the active
Object Promotion to Old Generation
Objects transition (promote) from the Young Generation to the Old Generation under three distinct circumstances:
- Reaching Tenuring Threshold: An object survives enough Minor GCs to reach the maximum age limit (
-XX:MaxTenuringThreshold, default 15). - Dynamic Tenuring Exceeded (Premature Promotion): If cumulative survivor volume exceeds the target survivor capacity (
-XX:TargetSurvivorRatio), younger objects are promoted early to prevent Survivor space overflow. - Direct Humongous Allocation: Objects or arrays that are too large to fit in Eden or exceed 50% of a G1 region bypass the Young Generation entirely and are allocated directly into the Old Generation.
PermGen vs. Metaspace (Java 8 Paradigm Shift)
Prior to Java 8, class metadata resided in PermGen (Permanent Generation) inside the Java Heap:
| Dimension | PermGen (Java 7 and earlier) | Metaspace (Java 8+) |
|---|---|---|
| Location | Contiguous segment inside Java Heap | Allocated in Native OS Memory (Off-Heap) |
| Sizing | Fixed ceiling (-XX:MaxPermSize=128m); caused frequent crashes |
Auto-expands up to available OS RAM (-XX:MaxMetaspaceSize) |
| String Constant Pool | Stored in PermGen (moved to Heap in Java 7) | Stored in Java Heap |
| Static Class Variables | Stored in PermGen | Stored in Java Heap (inside java.lang.Class instances) |
| Failure Mode | java.lang.OutOfMemoryError: PermGen space |
java.lang.OutOfMemoryError: Metaspace |
🔴 Senior Level
Dynamic Tenuring Threshold (-XX:TargetSurvivorRatio)
Configuring -XX:MaxTenuringThreshold=15 does not guarantee that an object will survive 15 Minor GCs before promotion.
HotSpot dynamically computes the actual promotion threshold at the conclusion of every Minor GC to protect Survivor spaces from exhaustion:
- The JVM compiles an Age Distribution Table of all surviving objects:
- Volume at Age 1 = $V_1$
- Volume at Age 2 = $V_2$
- Volume at Age $k$ = $V_k$
- It calculates the running cumulative sum: \(S_k = \sum_{i=1}^{k} V_i\)
- By default,
-XX:TargetSurvivorRatio=50(50% of Survivor capacity). - If $S_k > \text{TargetSurvivorCapacity}$, the JVM sets the effective tenuring threshold to $k$!
- All objects with $\text{Age} \ge k$ are immediately promoted to the Old Generation, even if their age is far below 15.
The 4-Bit Mark Word Constraint: Why MaxTenuringThreshold $\le 15$
A classic senior interview question: “Why can you never configure -XX:MaxTenuringThreshold higher than 15?”
In 64-bit HotSpot, the physical layout of an object’s Mark Word is constrained by hardware and performance requirements:
64-bit Mark Word Layout (Normal / Unlocked Object):
┌─────────────────────────┬──────────────────────┬─────────┬────────────┬─────────┐
│ HashCode (31 bits) │ Unused (25 bits) │ Age (4) │ Biased (1) │ Tag (2) │
└─────────────────────────┴──────────────────────┴─────────┴────────────┴─────────┘
▲
│
Only 4 bits allocated for Age!
- Exactly 4 bits in the Mark Word are allocated to track an object’s age.
- With 4 bits, the maximum representable binary integer is: \(2^4 - 1 = 16 - 1 = 15 \quad (1111_2 = 15_{10})\)
- Attempting to pass
-XX:MaxTenuringThreshold=16causes the JVM to fail at startup with an argument out-of-bounds error.
Modern Evolution: Generational ZGC (Java 21+ JEP 439)
When ZGC was introduced in Java 11–17, it was single-generational: it treated the entire heap as a single flat space and collected everything concurrently.
While single-generational ZGC achieved sub-millisecond pauses, it suffered severe throughput penalties on write-heavy workloads because background GC threads had to scan millions of short-lived objects across the entire multi-terabyte heap.
In Java 21, Generational ZGC (-XX:+UseZGC -XX:+ZGenerational) married generational theory with concurrent colored-pointer mechanics:
- Young and Old generations are tracked independently.
- The Young Generation is collected frequently and concurrently, recycling infant objects before they ever reach Old Gen.
- Delivers a 4x reduction in CPU allocation stalls and slashes CPU usage by ~50% compared to legacy ZGC while preserving the $<1\text{ ms}$ pause guarantee.
4 Tricky Questions
1. Why does the Young Generation require two Survivor spaces (S0 and S1) instead of just one?
Answer: The dual-survivor design enables the JVM to use a high-speed Copying (Evacuation) algorithm that completely eliminates memory fragmentation without paying the heavy CPU cost of sliding memory compaction. If only one Survivor space existed, surviving objects from Eden and the single Survivor space would have to be compacted in-place or intermingled with dead objects, leaving fragmented holes that slow down subsequent allocations. With two spaces, one space is designated as To-space and is guaranteed to be 100% empty. Live objects are copied contiguously into To-space using instant pointer bumping, while Eden and the From-space are completely wiped clean in a single pointer reset.
2. Why is the JVM flag -XX:MaxTenuringThreshold strictly capped at 15?
Answer: The limit is determined by the physical memory layout of the 64-bit JVM Mark Word in the object header. Exactly 4 bits are reserved for the object’s age counter (age bits). In unsigned binary representation, 4 bits can store integers from $0000_2$ (0) to $1111_2$ (15). Representing the number 16 would require a 5th bit, which does not exist in the Mark Word specification without stealing bits from the identity hashcode or lock-state metadata. Consequently, the JVM specification hard-codes 15 as the absolute ceiling.
3. Is Metaspace part of the Java Heap, and where are static class variables stored in modern Java?
Answer: No, Metaspace is not part of the Java Heap; it is allocated out of the operating system’s native memory (Off-Heap). Prior to Java 8, class metadata resided in PermGen inside the Java Heap. In Java 8 and later, PermGen was eliminated, and class metadata (bytecode, constant pools) moved to native Metaspace. However, static class variables are NOT stored in Metaspace; they reside in the Java Heap as fields of the java.lang.Class mirror object. Similarly, the String Constant Pool is located inside the Java Heap.
4. What is the Dynamic Tenuring Threshold, and how does -XX:TargetSurvivorRatio influence premature object promotion?
Answer: The Dynamic Tenuring Threshold is an adaptive promotion age calculated by the JVM at the end of each Minor GC to prevent Survivor spaces from overflowing. While -XX:MaxTenuringThreshold defines the upper age ceiling (e.g., 15), the JVM calculates a cumulative age histogram of surviving objects. If the sum of memory occupied by objects from age 1 up to age $k$ exceeds the target survivor ratio (configured via -XX:TargetSurvivorRatio, default 50%), the effective promotion threshold for that collection is dynamically reduced to $k$. All objects aged $k$ and older are immediately promoted to the Old Generation. If survivor spaces are undersized, this leads to premature promotion and frequent Full GCs.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“JVM memory is partitioned into Generations based on the Weak Generational Hypothesis—the empirical observation that 90%+ of objects die shortly after creation. The Young Generation (Eden + dual Survivor spaces S0/S1) uses a high-speed, zero-fragmentation Copying algorithm during frequent Minor GCs. Objects surviving multiple cycles are promoted to the Old Generation, which is collected less frequently using mark-sweep-compact techniques. Metaspace resides in Native OS Memory, storing class metadata, while static variables and strings live in the Heap. The maximum promotion age is physically capped at 15 because an object’s Mark Word allocates exactly 4 bits for age tracking.”
Key Architectural Takeaways
- Weak Generational Hypothesis: The foundation of JVM GC: most objects die young; long-lived objects remain alive indefinitely.
- Young Gen Structure: Eden (80%) + S0 (10%) + S1 (10%); uses dual-survivor ping-pong copying to prevent fragmentation.
- Tenuring Age Limit: Strictly bounded between 0 and 15 due to the 4-bit limit in the object header’s Mark Word.
- Dynamic Tenuring: If survivor occupancy exceeds
-XX:TargetSurvivorRatio(default 50%), the promotion threshold drops below 15 dynamically. - Metaspace is Off-Heap: Replaced PermGen in Java 8; auto-expands in native OS RAM, while static fields and string literals live in the Heap.
- Generational ZGC (Java 21): Extends ZGC with generational separation, cutting CPU overhead by ~50% while preserving $<1\text{ ms}$ pauses.
Red Flags & Anti-Patterns
- 🚩 Claiming “Metaspace is located inside the Java Heap”: Fundamental error; Metaspace is in native OS memory.
- 🚩 Setting
-XX:MaxTenuringThresholdgreater than 15: Fails JVM startup verification due to 4-bit Mark Word limits. - 🚩 Believing objects are only promoted to Old Gen at age 15: Overlooks Dynamic Tenuring Threshold and Survivor overflow.
- 🚩 Assuming static variables live in Metaspace: Static variables live in the Java Heap inside the
Classobject. - 🚩 Undersizing Survivor spaces in high-throughput apps: Forces short-lived objects into Old Gen (Premature Promotion), causing frequent Full GCs.
Related Topics
- What is Young Generation — Deep dive into Eden, Survivors, and TLABs
- What is Old Generation (Tenured) — Tenured space allocation and promotion mechanics
- What is Metaspace (or PermGen) — Off-heap metadata and ClassLoader lifecycle
- What GC algorithms exist — Copying vs Mark-Sweep vs Mark-Compact
- What are -Xms and -Xmx parameters — Heap sizing flags and generational tuning