Understanding JVM Garbage Collection: Memory Allocation and Object Lifecycle

Heap Memory Structure

In the generational model:

  1. The Eden zone, s0 ("From"), and s1 ("To") zones comprise the Young Generation, while the Tenured zone represents the Old Generation. In most scenarios, new objects are initially allocated in the Eden region.
  2. Following a Young Generation garbage collection, surviving objects migrate to the s1 ("To") zone, and their age increments by 1. When an object's age reaches a specific threshold (defaulting to 15), it gets promoted to the Old Generation. This age threshold can be configured using the -XX:MaxTenuringThreshold parameter.
  3. After each GC cycle, the Eden and "From" regions are cleared. The roles of "From" and "To" are then swapped—what was "From" becomes the new "To," and vice versa. This mechanism ensures that the Survivor region named "To" remains empty. Minor GC repeats this process until the "To" region fills up, at which point all objects are moved to the Old Generation.

Object Allocation in Eden

Most contemporary garbage collectors utilize generational collection algorithms, necessitating the division of heap memory into Young and Old generations to apply appropriate collection strategies for each.

Under typical circumstances, objects are allocated within the Eden area of the Young Generation. When Eden lacks sufficient space for allocation, the JVM initiates a Minor GC.

GC Types Defined:

  • Minor GC (Young Generation GC): Garbage collection occurring in the Young Generation. These collections are frequent and generally complete quickly.
  • Major GC / Full GC (Old Generation GC): Collection targeting the Old Generation. Major GC is often accompanied by at least one Minor GC and typically takes over 10 times longer than a Minor GC.

Consider the following demonstration:

public class MemoryAllocationDemo {
    public static void main(String[] args) {
        byte[] dataChunk1, dataChunk2;
        dataChunk1 = new byte[30900 * 1024];
    }
}

Running with the -XX:+PrintGCDetails flag reveals that Eden is nearly fully allocated. When attempting to allocate additional memory:

dataChunk2 = new byte[900 * 1024];

Allocation Guarantee Mechanism

When Eden lacks space for a new allocation, the JVM triggers a Minor GC. If the existing large object cannot fit into Survivor space, the Allocation Guarantee Mechanism transfers it directly to the Old Generation, provided sufficient space exists there. This prevents a Full GC from occurring prematurely.

public class AllocationGuaranteeDemo {
    public static void main(String[] args) {
        byte[] blockA = new byte[32000 * 1024];
        byte[] blockB = new byte[1000 * 1024];
        byte[] blockC = new byte[1000 * 1024];
        byte[] blockD = new byte[1000 * 1024];
        byte[] blockE = new byte[1000 * 1024];
    }
}

Large Objects Enter Old Generation Directly

Large objects—those requiring substantial contiguous memory (such as large arrays or lengthy strings)—are allocated directly in the Old Generation. This strategy avoids the efficiency loss from copying large objects during the allocation guarantee mechanism.

Long-Lived Objects Promote to Old Generation

Since the JVM employs generational collection, it must determine which objects belong in the Young versus Old Generation. To accomplish this, each object maintains an age counter.

Objects surviving their first Minor GC in Eden are moved to a Survivor space with age 1. Each subsequent survival through a Minor GC increments this age. Up on reaching the threshold (default 15, configurable via -XX:MaxTenuringThreshold), objects are promoted to the Old Generation.

Dynamic Age Determination

During heap traversal, HotSpot accumulates object sizes by age. When the cumulative size of objects at a particular age exceeds half the Survivor space capacity, the JVM uses the smaller of this age or MaxTenuringThreshold as the new promotion threshold.

uint computePromotionThreshold(size_t survivorCapacity) {
    size_t targetSize = (size_t)(((double)survivorCapacity) * TargetSurvivorRatio / 100);
    size_t accumulated = 0;
    uint currentAge = 1;
    
    while (currentAge < maxTableSize) {
        accumulated += sizes[currentAge];
        if (accumulated > targetSize) {
            break;
        }
        currentAge++;
    }
    
    return (currentAge < MaxTenuringThreshold) ? currentAge : MaxTenuringThreshold;
}

Note: The default promotion age varies by garbage collector—15 for the parallel throughput collector, but 6 for CMS.

Determining Object Death

Before reclaiming heap memory, the garbage collector must identify which objects are no longer reachable.

Reference Counting Algorithm

This appproach adds a reference counter to each object, incrementing when referenced and decrementing when references expire. Objects with a counter of zero are considered unreachable. While simple and efficient, mainstream JVMs avoid this algorithm due to its inability to handle circular references.

public class CircularReferenceExample {
    Object reference;
    
    public static void main(String[] args) {
        CircularReferenceExample objA = new CircularReferenceExample();
        CircularReferenceExample objB = new CircularReferenceExample();
        
        objA.reference = objB;
        objB.reference = objA;
        
        objA = null;
        objB = null;
        // Despite nullifying references, circular reference prevents collection
    }
}

Reachability Analysis Algorithm

This algorithm uses "GC Roots" as starting points and traverses reference chains. Objects unreachable from any GC Root through any reference chain are deemed collectible.

Understanding Java Reference Types

Since JDK 1.2, Java categorizes references into four types with decreasing strength:

  1. Strong Reference: The default reference type. Objects with strong references are never collected, even under memory pressure. The JVM throws OutOfMemoryError rather than reclaiming such objects.
  2. Soft Reference: Objects with only soft references are collected when memory is insufficient. These are ideal for memory-sensitive caches. Soft references can be paired with a ReferenceQueue for notification when the referent is collected.
  3. Weak Reference: Objects with only weak references have a shorter lifecycle. The garbage collector reclaims them regardless of available memory, though discovery timing depends on the low-priority GC thread. Weak references also support ReferenceQueue pairing.
  4. Phantom Reference: The weakest reference type. Objects holding only phantom references may be collected at any time. Phantom references do not affect object lifecycle but instead track when objects are about to be collected. They must be used with a ReferenceQueue.

Objects Are Not Immediately "Dead"

Objects identified as unreachable undergo a two-phase marking process. The first mark identifies objects requiring finalize() execution. Objects without an overridden finalize() method, or those whose finalize() has already been invoked, proceed directly to collection. Objects scheduled for finalize() get a second chance—if they establish a connection to the reference chain, they survive; otherwise, they are reclaimed.

Identifying Discarded Constants

The runtime constant pool reclaims discarded constants. A constant is considered discarded when no String object references it. Note that since JDK 1.7, the runtime constant pool resides in the heap rather than the method area.

Identifying Useless Classes

Class unloading in the method area requires satisfying three conditions:

  • All instances of the class have been collected (no instances exist in the heap).
  • The ClassLoader that loaded the class has been collected.
  • The java.lang.Class object is not referenced any where, preventing reflection access.

Satisfying these conditions permits but does not guarantee class unloading.

Garbage Collection Algorithms

Mark-Sweep Algorithm

This foundational algorithm operates in two phases: marking all collectible objects, then sweeping them. Its drawbacks include execution efficiency concerns and memory fragmentation from scattered free spaces.

Copying Algorithm

To address efficiency, this approach divides memory into two equal halves. When one half fills, surviving objects are copied to the other half, and the original half is cleared entirely. This eliminates fragmentation but wastes half the available memory.

Mark-Compact Algorithm

Designed for the Old Generation, this algorithm marks objects identically to Mark-Sweep but then compacts all surviving objects to one end of the memory region, clearing everything beyond the boundary. This eliminates fragmentation while preserving memory efficiency.

Generational Collection Algorithm

Current JVM implementations combine these approaches based on generational characteristics:

  • Young Generation: High mortality rate makes the Copying algorithm efficient, as only a small percentage of objects survive each collection.
  • Old Generation: High survival rate and lack of guarantee space necessitate Mark-Sweep or Mark-Compact algorithms.

Tags: JVM garbage collection Java Heap Memory Management GC Algorithms

Posted on Thu, 16 Jul 2026 17:21:54 +0000 by sridsam