How Garbage Collection Works
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
| Phase | What happens |
|---|---|
| Mark | Trace from the roots and mark every reachable object |
| Sweep | Reclaim the memory of every unmarked object |
| Compact | Move 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:
| Area | Holds | Collected by |
|---|---|---|
| Eden (young) | Newly created objects | Frequent, fast minor (young) collections |
| Survivor spaces (young) | Objects that survived a few young collections | Copied back and forth, aging each time |
| Old generation | Objects 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.
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());
}
}Young collections ran? true
Full old-generation collections: 0
Long-lived objects kept: 100Explanation: 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
- Easy: List three kinds of GC roots and give a code example of each.
- Harder: Change
Generationsto keep every 10th array instead of every 1,000th and run it with-Xmx64m -Xlog:gc. What changes in the log?
Key Takeaways
Was this tutorial helpful?