BackendBench
Coffee

Unravel โ€บ Java concepts โ€บ JVM and performance

Java language

Memory leaks in Java

Java has a garbage collector, yet leaks happen: objects that are still reachable but never used again.

Learn these first

At a glance

Mental model

A hoarder with a perfectly good cleaning crew: the crew never throws away anything that is still on a shelf, so the house fills up with things nobody will use again.

What happens, in order
  1. 1Static collections: static final Map<String, Session> SESSIONS that only gets put calls keeps every session alive until the JVM exits
  2. 2Unbounded caches and queues: a HashMap used as a cache with no size limit or expiry; a producer faster than its consumer
  3. 3Listeners and callbacks never unregistered, inner classes or lambdas that capture a big outer object, ThreadLocal values never removed, and class loaders kept by threads or JDBC drivers after a redeploy
  4. 4Resources: unclosed streams, connections and statements leak native memory and file handles even if the Java objects are collected
Remember
Watch out
A static map used as a cache without evictionRegistering listeners and never removing themForgetting ThreadLocal.remove() in poolsHolding on to whole request objects in long-lived structures
Example (highlighted lines explain it)
import java.util.*;public class Sessions {  private static final Map<String, byte[]> CACHE = new HashMap<>();  static byte[] load(String key) {    return CACHE.computeIfAbsent(key, k -> new byte[1_000_000]);  }  static int size() { return CACHE.size(); }}
Read the full explanation (about 2 min)

Java has a garbage collector, yet leaks happen: objects that are still reachable but never used again. The typical culprits are static collections that only grow, caches without limits, forgotten listeners and ThreadLocals in pools.

A mental model

A hoarder with a perfectly good cleaning crew: the crew never throws away anything that is still on a shelf, so the house fills up with things nobody will use again.

How it works

Static collections: static final Map<String, Session> SESSIONS that only gets put calls keeps every session alive until the JVM exits.

Unbounded caches and queues: a HashMap used as a cache with no size limit or expiry; a producer faster than its consumer.

Listeners and callbacks never unregistered, inner classes or lambdas that capture a big outer object, ThreadLocal values never removed, and class loaders kept by threads or JDBC drivers after a redeploy.

Resources: unclosed streams, connections and statements leak native memory and file handles even if the Java objects are collected.

Going deeper

Symptoms: heap usage after each full GC creeps up; GC time grows; finally OutOfMemoryError. Confirm with a heap dump (jcmd <pid> GC.heap_dump, -XX:+HeapDumpOnOutOfMemoryError) and open it in Eclipse MAT or VisualVM: look at the biggest "dominator" and follow the path to a GC root.

Use bounded caches (Caffeine with maximumSize and expireAfterWrite), clean up in finally or @PreDestroy, and prefer try-with-resources.

Leaks are about lifetime, not size: put a clear owner and end of life on every object that is added to a long-lived structure.

Under the hood

The collector only knows reachability, not intent. A leaked object is reachable from a GC root (a static field, a running thread's stack, a class loader) through an unbroken chain of references; the heap dump's "path to GC roots" shows that chain.

Common mistakes

  • A static map used as a cache without eviction.
  • Registering listeners and never removing them.
  • Forgetting ThreadLocal.remove() in pools.
  • Holding on to whole request objects in long-lived structures.

How Unravel recognises it

When you paste code, Unravel looks for these signs of memory leaks in java. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is Memory leaks in Java?

Java has a garbage collector, yet leaks happen: objects that are still reachable but never used again. The typical culprits are static collections that only grow, caches without limits, forgotten listeners and ThreadLocals in pools.

What are the most common mistakes with Memory leaks in Java?

A static map used as a cache without eviction. Registering listeners and never removing them. Forgetting ThreadLocal.remove() in pools. Holding on to whole request objects in long-lived structures.

How can I see Memory leaks in Java in real code?

Paste your own Java into Unravel and it will point out where memory leaks in java appears, explain how it works at the detail level you choose, and draw the flow of the code around it.