Lesson Completion
Back to course

Memory Leaks: Detection and Prevention

Beginner
12 minutes4.6Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: Memory leaks in Java = unintentional object retention (objects reachable but not needed).
  • Common causes: (1) Static collections (grow forever), (2) Listeners not removed (event handlers), (3) Unclosed resources (streams, connections), (4) ThreadLocal (not cleared), (5) Inner classes (hold outer reference).
  • Symptoms: Growing heap, frequent GC, OutOfMemoryError.
  • Detection: Heap dumps, profilers (JVisualVM, Eclipse MAT).
  • Prevention: Clear collections, remove listeners, use try-with-resources, clean ThreadLocal!

Think of email inbox. Memory leak = never deleting emails (mailbox grows forever, eventually full). Static collection = "save all" folder. Listeners = subscriptions you forgot to cancel. Fix = regular cleanup, unsubscribe, delete old emails!


2. Conceptual Clarity (The "Simple" Tier)

💡 The Analogy

  • Memory leak: Hoarder's house (never throw anything away)
  • Static collection: Attic (stuff accumulates)
  • Listener: Magazine subscription (forgot to cancel)

3. Technical Mastery (The "Deep Dive")

Common Memory Leak Patterns

PatternCauseFix
Static collectionsNever clearedBound size, WeakHashMap
ListenersNot removedExplicit removal
Unclosed resourcesNo close()try-with-resources
ThreadLocalNot removedremove() in finally
Inner classesOuter referenceStatic inner class

4. Interactive & Applied Code

java
import java.util.*; public class MemoryLeakDemo { public static void main(String[] args) { demonstrateStaticCollectionLeak(); demonstrateListenerLeak(); demonstrateThreadLocalLeak(); demonstrateInnerClassLeak(); } // ❌ LEAK 1: Static collection static List<byte[]> CACHE = new ArrayList<>(); // Grows forever! static void demonstrateStaticCollectionLeak() { System.out.println("=== STATIC COLLECTION LEAK ==="); // ❌ BAD: Unbounded cache for (int i = 0; i < 1000; i++) { CACHE.add(new byte[1024 * 1024]); // 1MB per item // CACHE nev er cleared → LEAK! } // ✅ FIX 1: Bounded cache // Use LRU cache (e.g., LinkedHashMap with removeEldestEntry) // ✅ FIX 2: WeakHashMap (entries GC'd when key not strongly referenced) } // ❌ LEAK 2: Listeners not removed static class EventSource { List<EventListener> listeners = new ArrayList<>(); void addListener(EventListener listener) { listeners.add(listener); } void removeListener(EventListener listener) { listeners.remove(listener); } } interface EventListener { void onEvent(); } static void demonstrateListenerLeak() { System.out.println("\n=== LISTENER LEAK ==="); EventSource source = new EventSource(); // ❌ BAD: Add listener but never remove for (int i = 0; i < 1000; i++) { EventListener listener = () -> System.out.println("Event"); source.addListener(listener); // Listener never removed → LEAK! } // ✅ FIX: Remove listeners // source.removeListener(listener); } // ❌ LEAK 3: ThreadLocal not cleared static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>(); static void demonstrateThreadLocalLeak() { System.out.println("\n=== THREADLOCAL LEAK ==="); // ❌ BAD: Set ThreadLocal but never remove threadLocal.set(new byte[1024 * 1024]); // 1MB // Thread reused in pool → ThreadLocal persists → LEAK! // ✅ FIX: Always remove in finally try { threadLocal.set(new byte[1024 * 1024]); // Use threadLocal } finally { threadLocal.remove(); // Clean up! } } // ❌ LEAK 4: Inner class holds outer reference class OuterClass { byte[] data = new byte[1024 * 1024]; // 1MB class InnerClass { // Implicitly holds reference to OuterClass! void doSomething() { System.out.println("Inner"); } } InnerClass createInner() { return new InnerClass(); } } static void demonstrateInnerClassLeak() { System.out.println("\n=== INNER CLASS LEAK ==="); OuterClass outer = new OuterClass(); OuterClass.InnerClass inner = outer.createInner(); // ❌ LEAK: 'inner' holds reference to 'outer' (including 1MB data!) // Even if we don't need 'outer', it's kept alive by 'inner' // ✅ FIX: Use static inner class (no outer reference) // static class InnerClass { ... } } } // Real-world example: Connection pool leak class ConnectionPoolLeak { static List<Connection> pool = new ArrayList<>(); static Connection getConnection() { Connection conn = new Connection(); pool.add(conn); // ❌ LEAK: Never removed from pool! return conn; } // ✅ FIX: Return to pool or close static void releaseConnection(Connection conn) { pool.remove(conn); conn.close(); } } class Connection { void close() { System.out.println("Connection closed"); } } // Unclosed resources leak class ResourceLeak { static void demonstrateLeak() throws Exception { // ❌ BAD: Resource not closed java.io.FileInputStream fis = new java.io.FileInputStream("file.txt"); // If exception thrown, fis never closed → LEAK! // ✅ FIX: try-with-resources try (java.io.FileInputStream fis2 = new java.io.FileInputStream("file.txt")) { // Auto-closed even if exception } } }

5. The Comparison & Decision Layer

Leak TypeDetectionPrevention
Static collectionGrowing heapBound size, WeakHashMap
ListenerEvent source growsRemove listeners
ResourceFile/connection leaktry-with-resources
ThreadLocalThreadPool appsremove() in finally

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "Can Java have memory leaks?" Answer: YES! Even with GC!

java
// Memory leak example: static List<Object> LEAK = new ArrayList<>(); void method() { LEAK.add(new byte[1024 * 1024]); // 1MB // Objects never removed from LEAK // Still reachable (static) → GC can't collect // Heap grows forever → OutOfMemoryError } // NOT a leak: void method2() { List<Object> local = new ArrayList<>(); local.add(new byte[1024 * 1024]); // After method returns: 'local' unreachable → GC collects }

Pro-Tips:

  1. Detecting leaks:
bash
# Heap dump on OOM java -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/tmp/heap.hprof \ -jar app.jar # Analyze with Eclipse MAT # Look for: # - Dominator tree (what's holding memory) # - Leak suspects (accumulating collections) # - Histogram (object counts)
  1. WeakHashMap for caches:
java
// ✅ Entries GC'd when key not strongly referenced Map<Object, Object> cache = new WeakHashMap<>(); Object key = new Object(); cache.put(key, value); key = null; // No more strong refs // Next GC → entry removed from WeakHashMap!

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01