How Does Java Garbage Collection Manage Heap Memory?

The Java Garbage Collector (GC) automates dynamic memory management by allocating heap space for newly instantiated objects and reclaiming memory occupied by objects that are no longer reachable by the application. Instead of relying on manual deallocation, Java tracks reference trees rooted in active execution contexts, divides the heap into distinct generations based on object lifespan, and executes specialized collection algorithms to minimize throughput overhead and pause times.

Core Memory Layout: The Generational Hypothesis

The design of the Java Virtual Machine (JVM) heap relies heavily on the Weak Generational Hypothesis, which observes two primary patterns in enterprise workloads: most objects become unreachable shortly after creation, and references from older objects to newer objects are relatively rare.

To capitalize on this behavior, the heap is split into two primary physical areas:

  • Young Generation: The landing zone for newly created instances. It is subdivided into:

  • Eden Space: Where all objects are initially allocated.

  • Survivor Spaces (S0 and S1 / From and To): Two semi-spaces of identical size used to retain objects that survive initial collection cycles.

  • Old Generation (Tenured): Reserved for long-lived objects that have survived multiple collection cycles in the Young Generation, as well as exceptionally large objects that bypass Young Generation buffers entirely to prevent excessive copying overhead.

The Allocation Lifecycle and TLABs

Allocation performance is critical in multi-threaded Java applications. To avoid thread contention on a single heap allocation pointer, the JVM uses Thread-Local Allocation Buffers (TLABs).

Each thread is assigned a dedicated chunk within the Eden space. When a thread requests memory via the new keyword, the JVM uses a bump-the-pointer technique inside that thread's local TLAB:

  1. The thread checks if its TLAB has sufficient contiguous space for the object.
  2. If space exists, the pointer advances by the size of the instance, returning the memory address without synchronization locks.
  3. If the TLAB is full, the thread requests a new buffer from the shared Eden space (requiring synchronization) or allocates directly in shared Eden if the object exceeds TLAB limits.

Identifying Unreachable Objects: Tracing Reachability

The garbage collector determines whether an object's memory can be reclaimed through Reachability Analysis rather than reference counting. Reference counting fails to detect circular references, whereas reachability analysis determines viability based on accessibility from a set of starting references known as GC Roots.

GC Roots include:

  • Local variables and parameters active within thread call stacks.
  • Active Java threads.
  • Static fields held by loaded classes.
  • JNI (Java Native Interface) global and local references.
  • Objects utilized as synchronization monitors.

During collection, the GC traverses object references starting at these roots. Any object reachable through a continuous chain of references is marked as alive; any unvisited object is marked for reclamation.

Reclamation: Minor vs. Major Collections

Garbage collection runs at different intervals and granularities across the heap regions:

Minor GC (Young Generation)

When Eden exhausts its available capacity, the JVM triggers a Minor GC:

  1. Live objects in Eden and the active Survivor space ("From") are identified.
  2. Survivors are copied into the alternate Survivor space ("To"), packing them contiguously to eliminate fragmentation.
  3. Eden and the previously active Survivor space are cleared entirely.
  4. Each time an object survives a cycle, its internal tenure age increments. Once an object exceeds the aging threshold (configured by -XX:MaxTenuringThreshold), it is promoted to the Old Generation.

Major or Full GC (Old Generation)

When the Old Generation approaches capacity or fails to satisfy an incoming promotion from the Young Generation, a collection cycle targeting tenured memory begins. Because the Old Generation contains significantly more live references, algorithms prioritize low fragmentation and low pause times using techniques such as mark-sweep-compact or concurrent region evacuation.

Modern Garbage Collection Implementations

Modern production JVMs provide distinct collectors tailored for differing latency and throughput constraints:

  • G1 GC (Garbage-First): Partitions the heap into equal-sized, non-contiguous regions rather than static memory blocks. It continuously tracks the volume of reclaimable space across regions and prioritizes collecting regions containing the most garbage within a user-defined pause-time target.
  • ZGC and Shenandoah: Low-latency collectors that perform marking, evacuation, and reference updating concurrently with application thread execution. By using colored pointers, load barriers, or Brooks pointers, they reduce stop-the-world pauses to sub-millisecond ranges regardless of heap size.