All topics
memory management
advanced

Memory Leaks & Reference Types

Recognize how unintended references prevent GC, and use Strong / Soft / Weak / Phantom references to control object lifetime.

Java frees memory for you, and Java programs still run out of it.

Strong = a leash you're holding. Soft = a leash on a hook (cut only if the room is full). Weak = a sticky note (gone with the next cleaning). Phantom = a notification when the object is being thrown out.

Key Concepts

1
A leak here does not mean the collector missed something. It means your program is still holding a reference to an object it will never use again, so the collector is not allowed to free it.
2
The four kinds of reference, and what each tells the collector.
  Strong    an ordinary field or variable
            "never collect this"
  Soft      "collect this if memory gets tight"
  Weak      "collect this as soon as nothing strong points at it"
  Phantom   "tell me after this has been collected"
3
You use strong references without thinking — every = creates one. The other three exist for caching and cleanup.
=
4
A cache wants soft references: keep entries while there is room, drop them under pressure. A lookup table keyed by objects you do not own wants weak references, so entries disappear when the rest of the program forgets the key. WeakHashMap does exactly that.
WeakHashMap
5
The leaks that actually happen.A collection that only grows. A static map used as a cache, with nothing ever removed. This is the most common Java leak by a distance.
static
6
Listeners nobody removes. You register a callback, the publisher stores a strong reference, and your object can never be collected. Every object that ever registered stays alive.
7
Inner classes holding their parent. A non-static inner class keeps a hidden reference to the object that created it. A long-lived Runnable defined inside a large object keeps that entire object alive.
Runnable
8
ThreadLocal in a thread pool. This one surprises people. The value is attached to the thread, and a pooled thread lives forever, so the value is never released:
ThreadLocal
9
    try {
        context.set(userContext);
        handleRequest();
    } finally {
        context.remove();       // without this it leaks, and the next
    }                           // request may see the previous user's data
10
How to find one. Do not watch memory usage — it rises and falls normally. Watch the level it returns to after a full collection. In a healthy application that floor is steady; in a leaking one it climbs over hours.
11
Then take two heap snapshots an hour apart under load and compare them. Look for the class whose instance count grew, then find what is holding it.
12
What the interviewer is probing.1. "Can Java leak memory?" Probing: what the collector does and does not do. Stalls: "No, there is a garbage collector." Moves up: it frees unreachable objects; a leak is an object still reachable and never used again.
13
2. "Give a real example." Probing: experience. Stalls: "Not closing resources." Moves up: an unbounded static cache, a listener never removed, or a ThreadLocal on a pooled thread — with why the reference survives.
ThreadLocal
14
3. "When would you use a weak reference?" Probing: purpose. Stalls: "To help the collector." Moves up: for data keyed by an object you do not own, so it disappears with the key. Caches want soft, not weak.
15
4. "How would you find one?" Probing: method. Stalls: "Read the code." Moves up: the post-collection floor, then two heap snapshots compared.
16
5. "Why is ThreadLocal risky in a pool?" Probing: lifetime mismatch. Stalls: "It is per-thread, so it is fine." Moves up: pooled threads are reused forever, so the value is never released and can leak between requests.
ThreadLocal
17
  Reference strengths, and what each is for
18
  Type      Collected when            Cleared by GC?   Use for
  ------------------------------------------------------------------
  Strong    never while reachable     no               ordinary code
  Soft      memory pressure           last resort      memory-sensitive
                                                       caches
  Weak      next GC after strong      eagerly          metadata keyed
            refs are gone                              by foreign
                                                       objects
  Phantom   already finalised;        always           deterministic
            get() always returns                      native cleanup
            null                                       (replaces
                                                       finalize)
19
  Typical leak shapes and the fix
20
  static Map cache = new HashMap<>();      -> bounded cache with
                                              eviction (Caffeine),
                                              or WeakHashMap keys
21
  publisher.addListener(this);             -> removeListener in the
                                              matching teardown
22
  ThreadLocal<Ctx> ctx = ...               -> try { ... }
    (in a pooled thread)                      finally { ctx.remove(); }
23
  new Runnable(){...} inside a big object  -> static nested class, or
                                              a lambda that captures
                                              only what it needs