How Garbage Collection Works

Naveen AnamaneniUpdated · 5 min read

Garbage collectors differ in their details, but they all rest on the same ideas. They start from a set of GC roots, mark every object reachable from them, and then reclaim the rest, either by sweeping it away or by copying the live objects somewhere else. Most collectors also split the heap into generations, because most objects die very young. In this lesson, you will learn these building blocks: GC roots, mark and sweep, copying and compaction, and generational collection.

GC Roots

A GC root is a reference the collector treats as always alive. The main roots are:

  • Local variables and parameters on the stacks of live threads,
  • Static fields of loaded classes,
  • Active threads themselves,
  • JNI references held by native code,
  • Objects used as locks by threads that are waiting on them.

Everything reachable from a root, directly or through other objects, is live. Everything else is garbage.

Mark, Sweep and Compact

Mark the live objects, reclaim the rest, then squeeze the survivors together
PhaseWhat happens
MarkTrace from the roots and mark every reachable object
SweepReclaim the memory of every unmarked object
CompactMove live objects together to remove gaps (fragmentation)
Copy (alternative)Copy live objects into an empty area; the old area is then entirely free

Copying is fast when few objects survive, because only live objects are touched.

Generational Collection

Most objects die young: temporary strings, iterators, request objects. So collectors split the heap:

AreaHoldsCollected by
Eden (young)Newly created objectsFrequent, fast minor (young) collections
Survivor spaces (young)Objects that survived a few young collectionsCopied back and forth, aging each time
Old generationObjects that survived long enough (promoted)Less frequent major or mixed collections

A young collection copies the few live objects from eden to a survivor space and simply empties eden. Objects that survive several collections are promoted to the old generation.

Example: Short-Lived vs Long-Lived Objects

Let's see an example that tracks young and old collections while a program creates many temporary objects and keeps a few.

Generations.java▶ Try it
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
 
public class Generations {
    public static void main(String[] args) {
        List<byte[]> longLived = new ArrayList<>();
        for (int i = 0; i < 100_000; i++) {
            byte[] temp = new byte[2_000];
            if (i % 1000 == 0) {
                longLived.add(temp);
            }
        }
        for (GarbageCollectorMXBean gc :
                ManagementFactory.getGarbageCollectorMXBeans()) {
            if (gc.getName().contains("Young")) {
                System.out.println("Young collections ran? "
                    + (gc.getCollectionCount() > 0));
            }
            if (gc.getName().contains("Old")) {
                System.out.println("Full old-generation collections: "
                    + gc.getCollectionCount());
            }
        }
        System.out.println("Long-lived objects kept: " + longLived.size());
    }
}
Output
Young collections ran? true
Full old-generation collections: 0
Long-lived objects kept: 100

Explanation: The program creates 100,000 temporary 2 KB arrays (about 200 MB) and keeps only 100 of them. Cheap young collections handle all that garbage; no full collection of the old generation was needed. This is the pay-off of generational collection: work is proportional to the live objects, not the garbage.

A Warning Sign

Common Mistakes

Thinking reference counting is how Java collects garbage. Java traces from roots, which also handles cycles.

Assuming full collections are normal. Frequent full GCs usually mean the heap is too small or something leaks.

Interview Corner

How does garbage collection work in Java?

The collector starts from GC roots, such as live thread stack slots, static fields and JNI references, and traces all reachable objects (marking). Unreachable objects are reclaimed, either by sweeping or by copying live objects to another region, and most collectors compact to avoid fragmentation. HotSpot collectors are generational: new objects are allocated in eden, survivors of young collections are copied to survivor spaces and eventually promoted to the old generation, which is collected less often. This works well because most objects die young.

Practice

  1. Easy: List three kinds of GC roots and give a code example of each.
  2. Harder: Change Generations to keep every 10th array instead of every 1,000th and run it with -Xmx64m -Xlog:gc. What changes in the log?

Quick check: How GC works

4 questions
1What does the mark phase do?
2Which of these is a GC root?
3Where are new objects usually allocated?
4Why is copying collection fast for the young generation?

Key Takeaways

Was this tutorial helpful?

Lesson 440 of 482