Java Memory Leak Patterns
In Java, memory leaks are subtle. Even though the Garbage Collector (GC) automatically clears objects that are no longer reachable, a “leak” occurs when your code maintains an accidental reference to an object that is no longer needed. This prevents the GC from reclaiming that memory.
Get Kandaanusha’s stories in your inbox
Join Medium for free to get updates from this writer.
Here are the most common patterns that lead to memory leaks in Java applications:
1. Static Collection Members
The static keyword links a variable to the Class itself rather than an instance. Because classes are rarely unloaded, any collection (like a HashMap or List) marked as static will persist for the lifetime of the JVM.
- The Pattern: Adding objects to a
staticlist and never removing them. - The Fix: Use non-static collections where possible, or remember to call
.clear()or remove entries manually when they are finished.Can useWeakHashMapas well
More details here.
2. Custom Cache Implementations
Developers often try to build a “simple cache” using a standard HashMap. However, a Map is not a cache unless it has an eviction policy.
- The Pattern: ```java private Map<String, Data> cache = new HashMap<>(); public Data get(String key) { return cache.computeIfAbsent(key, k -> loadFromDB(k)); }
This map will grow until the application crashes with an `OutOfMemoryError`.
- The Fix: Use
LinkedHashMapwith itsremoveEldestEntryfeature, or better yet, a professional library like Guava or Caffeine which supports time-based and size-based expiration.
3. Missing hashCode() and equals()
This is a “silent” leak that is very hard to spot.
- The Leak: If you use a custom object as a key in a
HashMapbut don't implementequals()andhashCode(), the Map cannot find the key to overwrite it. Every time you "put" an entry, it adds a new one instead of updating the old one.This causes the map to grow indefinitely. - The Fix: Always generate
equals()andhashCode()for any class used in aSetor as aMapkey.
More details here
4. Unclosed Resources
Resources like database connections, network sockets, and file streams occupy memory outside the standard heap. If you don’t close them, they remain “alive” in the eyes of the OS.
- The Pattern: Opening a
FileInputStreamor aJDBC Connectionwithout afinallyblock or try-with-resources. - The Fix: Always use the try-with-resources statement introduced in Java 7.
// Example of the "Good" pattern
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
// handled automatically
}5. Inner Classes Holding Outer Class References
In Java, a non-static inner class holds an implicit reference to its outer class.
- The Leak: If you create an inner class (like a
Runnableor aTimerTask) and pass it to a long-running thread, the entire outer class (which might be a largeActivityorService) stays in memory as long as that thread is running. - The Fix: Make inner classes
staticif they don't need to access the outer class's fields.
6. Over-sized ThreadLocals
ThreadLocal variables are tied to the thread they are created in. In modern web servers (like Tomcat), threads are pooled and reused. If you set a value in a ThreadLocal and don't call .remove(), that object will live as long as the thread lives—which could be the entire duration of the server's uptime.
- The Pattern: Storing large objects in a
ThreadLocalwithin a thread-pooled environment. - The Fix: Always call
threadLocal.remove()in afinallyblock.
7. Thread Death and “Zombie” Threads
If you manually create threads instead of using an ExecutorService, and those threads get stuck in a blocked state (e.g., waiting for a socket that never times out), they never terminate.
- The Pattern: Starting a new
Threadfor every task without a timeout or a way to interrupt it. - The Fix: Always use Thread Pools and ensure all I/O operations have an explicit
SO_TIMEOUT.
8. Heavy use of Large Arrays (Heap Fragmentation)
While technically not a “leak” in the sense of lost references, allocating massive arrays (like byte[100000000]) frequently can lead to Premature Promotion or heap fragmentation. The GC might not find a contiguous block of memory large enough, even if the total free memory is sufficient.
- The Pattern: Allocating large buffers inside a loop or frequently resizing arrays.
- The Fix: Reuse buffers (Object Pooling) for large data processing tasks.
9. Forgotten Listeners and Callbacks
This is perhaps the most common leak in GUI applications (Swing, JavaFX) or event-driven systems. When you register a listener to an object that has a longer lifecycle than the listener itself, the object keeps a reference to the listener.
- The Pattern: Adding a
PropertyChangeListeneror a customEventListenerto a global or long-lived service and never callingremoveListener(). - The Fix: Use Weak References for listeners or ensure you unregister them in a “cleanup” or “dispose” method.
10. String.intern() in Older Java Versions
While modern Java (8+) has moved the “String Pool” to the regular heap, in older versions (Java 6 and below), String.intern() lived in the PermGen space, which was very limited in size and rarely garbage collected.
- The Pattern: Reading unique strings from a file or database and calling
.intern()on all of them. - The Fix: Avoid
intern()for large sets of dynamic data; use a manualWeakHashMapif you need to deduplicate strings.
References:
https://dzone.com/articles/how-memory-leaks-happen-in-java-apps



