Warm-up

Two objects, one collected

5 minintermediate28 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • The collector frees what is unreachable, not what you have finished with
  • A static field is a GC root, so anything it reaches lives for the process
  • A WeakReference is a probe for exactly the property that decides collection

Starter

Starter.javaOpen in playground
import java.lang.ref.*;
import java.util.*;

/**
 * WARM-UP — 5 minutes. One concept. Hard to fail.
 *
 * Two identical objects. Both have their local variable set to null. One is
 * collected and one is not.
 *
 * TASKS
 *   1. Predict both answers before running.
 *   2. Run it.
 *   3. In a comment: nothing about the two objects differs. So what does?
 *   4. Add one line before the second probe so that it is collected too.
 */
public class Starter {

    /** A static field. Its class is a GC root, and classes are not unloaded. */
    static final List<Object> CACHE = new ArrayList<>();

    public static void main(String[] args) {
        Object loose = new Object();
        WeakReference<Object> probe1 = new WeakReference<>(loose);
        loose = null;

        System.out.println("no other reference : collected = " + collected(probe1));

        Object cached = new Object();
        WeakReference<Object> probe2 = new WeakReference<>(cached);
        CACHE.add(cached);
        cached = null;

        // TASK 4: one line here makes the next line print true.

        System.out.println("added to CACHE     : collected = " + collected(probe2));
        System.out.println("CACHE.size()       : " + CACHE.size());

        // Question to answer in a comment before you move on:
        // CACHE is `final`. Does that have anything to do with why the second
        // object survived? If not, what would?
    }

    /**
     * System.gc() is a hint, not a command, so asking once proves nothing.
     * This asks repeatedly with allocation pressure in between, and gives up
     * after a bounded number of attempts so it cannot hang.
     */
    static boolean collected(WeakReference<?> ref) {
        for (int attempt = 0; attempt < 50 && ref.get() != null; attempt++) {
            System.gc();
            byte[] churn = new byte[1 << 20];
            if (churn.length < 0) System.out.print("");
        }
        return ref.get() == null;
    }
}

Run it locally:

cd exercises/java/jvm/memory-leaks/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You predicted which probe would clear before running
  • A comment explains what is different about the two objects — and it is not the objects
  • You made the second one collectable with a one-line change

← Back to How do you get a memory leak in a garbage-collected language?