How Java works

Pick a topic, write code and watch what happens inside, step by step.

Memory

Collections

Concurrency

Language and JVM

Topics

Garbage Collection

When is an object removed?

GC does not know whether an object is needed. It asks one question: can this object be reached through references from a GC root?

GC roots are local variables on the stack, static fields and running threads. A reached object is reachable and stays.

Step through on the right: the state changes as references are cut.

Cycles and mark-sweep

Can two objects that refer to each other be removed? With reference counting they could not. Java does not count references: it starts from the roots, so a cycle is no problem.

Mark: GC walks from the roots and marks what it reaches. Sweep: it frees the space of unmarked objects. A compact phase then packs the rest together.

During some phases the application can stop. This is called a stop-the-world pause.

Eden, Survivor and Old

Most objects die young, so the heap is split into generations. A new object starts in Eden. When Eden fills up, a Minor GC runs.

Minor GC copies only the survivors: they move to Survivor and age grows by 1. One Survivor is always empty. When age reaches the limit, the object moves to Old.

Old is checked less often. Try it: press new, click objects to remove their reference, then press Minor GC. A dead object in Old is not removed by Minor GC: it needs a Full GC.

How does a memory leak happen in Java?

GC exists, yet leaks still happen: if a reference to an unneeded object remains, GC cannot remove it. To GC it is still reachable.

The most common case is a static collection, then listeners that are never removed and unbounded caches. When the heap fills up you get OutOfMemoryError.

The fix is to remove the reference: remove, clear, a bounded cache or WeakReference.

Choosing a collector

All of them find reachable objects. They differ in when and how they work. Short pauses or more total work? Usually you cannot have the best of both.

Most of the time the default is fine. Measure with -Xlog:gc first, then decide.

Trap: finalize() is deprecated. Use try-with-resources to close resources.