All 15 Garbage Collection questions
More in Garbage Collection
Advertisement
Java › Garbage Collection

How would you diagnose a memory leak in a Java application (where memory usage grows unboundedly despite garbage collection running) using heap dump analysis, and what common patterns cause 'leaks' in a garbage-collected language?

Advanced

Answer

Despite automatic garbage collection, a Java application CAN still leak memory if objects remain unintentionally strongly reachable (via a reference chain the developer didn't intend to keep alive) -- common culprits include: an ever-growing static collection (like a cache implemented as a plain HashMap with no eviction policy), listener/observer registrations that are never unregistered (the 'lapsed listener' problem), ThreadLocal values not cleaned up in a pooled-thread environment (where the thread, and therefore its ThreadLocal storage, outlives the logical task that set the value), or classloader leaks in a plugin/hot-reload scenario -- diagnosing requires capturing a heap dump (via jmap, or automatically triggered on OutOfMemoryError via -XX:+HeapDumpOnOutOfMemoryError) and analyzing it with a tool like Eclipse MAT or VisualVM, specifically looking at the dominator tree and 'retained size' to identify which object(s) are unexpectedly keeping large portions of the heap alive, then tracing the GC root reference chain keeping them reachable.
# Capture a heap dump from a running JVM
jmap -dump:live,format=b,file=heapdump.hprof <pid>

# Automatically capture a heap dump on OutOfMemoryError
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -jar app.jar

# Then analyze heapdump.hprof with Eclipse Memory Analyzer (MAT),
# looking specifically at the 'Leak Suspects' report and dominator tree
Real-world example A team investigating steadily climbing memory usage in a long-running service captures a heap dump and, using Eclipse MAT's dominator tree view, discovers an unbounded static Map being used as an ad-hoc cache with entries added on every request but never evicted, tracing the specific code path responsible and adding a proper eviction policy (or switching to a bounded cache implementation) to resolve the leak.

Common follow-ups: Why can a memory leak still occur in a garbage-collected language despite the GC running correctly and doing exactly what it's designed to do?;What's the specific danger of ThreadLocal usage combined with a thread pool that reuses threads across many logical tasks?