The garbage collector frees heap objects that are no longer reachable — you never call free, but you still decide what stays reachable.
GC roots and reachability
Local variables and parameters in every live thread's stack frames.
Static fields of loaded classes.
Active threads themselves.
JNI references held by native code, and objects used as monitors (
synchronized).
public class Main {
static class Node { Node next; }
public static void main(String[] args) {
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a; // a cycle: a → b → a
a = null;
b = null; // no root reaches the cycle any more → both are garbage
System.out.println("the cycle is unreachable and will be collected");
}
}the cycle is unreachable and will be collectedGenerational collection
Weak generational hypothesis: most objects die young (temporary strings, iterators, DTOs). So the heap is split and the young part is collected often and cheaply.
Allocate in Eden
newbumps a pointer in a thread-local allocation buffer — allocation is extremely cheap.Minor GC
When Eden fills, live objects are copied to a Survivor space; everything left in Eden is reclaimed at once.
Age and promote
Each survived minor GC increments an object's age; past a threshold it is promoted to the old generation.
Major / full GC
When the old generation fills, it is collected too — more expensive, historically the source of long pauses.
| Phase / term | Meaning |
|---|---|
| Mark | Trace from the roots and mark every reachable object. |
| Sweep | Reclaim the memory of unmarked objects. |
| Compact | Slide live objects together to remove fragmentation. |
| Copying | Copy live objects to an empty space (used for the young gen: cost ∝ live objects, not heap size). |
| Stop-the-world (STW) | All application threads are paused while the GC works. |
| Concurrent | GC work done while application threads keep running. |
The collectors
| Collector | Flag | Best for | Notes |
|---|---|---|---|
| Serial | -XX:+UseSerialGC | Tiny heaps, single-core containers | One thread, stop-the-world |
| Parallel | -XX:+UseParallelGC | Batch jobs — maximum throughput | Many threads, still STW |
| G1 (default since 9) | -XX:+UseG1GC | General purpose, heaps of GBs | Region-based; targets a pause goal (-XX:MaxGCPauseMillis=200) |
| ZGC | -XX:+UseZGC (generational in 21: -XX:+ZGenerational) | Huge heaps, low latency | Pauses under a millisecond, mostly concurrent |
| Shenandoah | -XX:+UseShenandoahGC | Low latency | Concurrent compaction (OpenJDK builds) |
| CMS | — | — | Deprecated in 9, removed in 14 |
$ java -XX:+UseZGC -Xmx4g -Xlog:gc App.java # choose a collector, log each GC
$ java -Xlog:gc*:file=gc.log App.java # detailed GC log to a file
$ jcmd <pid> GC.heap_info # inspect a running JVMSystem.gc(), finalize() and cleanup
import java.lang.ref.Cleaner;
public class Main {
static final Cleaner CLEANER = Cleaner.create();
static class Resource implements AutoCloseable {
private final Cleaner.Cleanable cleanable;
Resource(String name) {
// the cleanup action must NOT capture `this`, or the object never becomes unreachable
cleanable = CLEANER.register(this, () -> System.out.println("released " + name));
}
@Override
public void close() { cleanable.clean(); } // runs the action at most once
}
public static void main(String[] args) {
try (Resource r = new Resource("db-connection")) {
System.out.println("using the resource");
}
System.out.println("after try");
}
}using the resource
released db-connection
after tryTuning in practice
Measure first: GC logs (
-Xlog:gc), JDK Flight Recorder (-XX:StartFlightRecording),jstat -gc <pid>.Size the heap: set
-Xms=-Xmxfor servers to avoid resizing.Pick the collector by goal: throughput → Parallel, balanced → G1, latency → ZGC.
Reduce garbage: reuse builders, avoid needless boxing, stream large files instead of loading them.
Can an object be garbage collected while it still references other objects?
What is a stop-the-world pause and how do modern collectors reduce it?
Minor GC vs major GC?
Does calling System.gc() guarantee collection?