Question
In a Java interview, I was asked how to create a memory leak in Java. I was not sure how to answer because Java has garbage collection, so I did not know how a leak could still occur.
What is a simple example of a memory leak in Java, and why does it happen?
For example, what kind of code would keep using memory unnecessarily instead of allowing it to be garbage-collected?
Short Answer
By the end of this page, you will understand what a memory leak means in Java, why garbage collection does not prevent all leaks, how to create a simple leak example, and how to recognize and avoid the patterns that cause leaks in real applications.
Concept
In Java, a memory leak does not usually mean memory becomes permanently unreachable in the same way it can in manual-memory languages like C or C++. Instead, it usually means:
- your program keeps references to objects it no longer needs
- because those references still exist, the garbage collector considers those objects reachable
- reachable objects are not collected
- memory usage keeps growing over time
This is the key idea: Java garbage collection only frees objects that are no longer reachable.
If your code accidentally stores objects in a collection, cache, static field, listener list, thread-local, or long-lived object, then those objects stay alive even if your program will never use them again.
A Java memory leak matters because it can cause:
- gradually increasing heap usage
- slower garbage collection
- degraded application performance
OutOfMemoryError- unstable long-running services
A leak is often not about "forgot to free memory". It is about forgot to release references.
Mental Model
Think of Java memory like a storage room managed by a cleaner.
- The garbage collector is the cleaner.
- The cleaner removes boxes only if no one has a label pointing to them.
- A reference is that label.
If you throw a box into a giant closet and keep the closet key forever, the cleaner sees the box as still owned and does not remove it.
So a Java memory leak is like saying:
"I do not need these boxes anymore, but I accidentally kept them in a closet that never gets emptied."
The problem is not that the cleaner failed. The problem is that your program kept saying the objects were still important.
Syntax and Examples
A common way to create a memory leak in Java is to keep adding objects to a long-lived collection and never remove them.
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakExample {
private static final List<byte[]> leak = new ArrayList<>();
public static void main(String[] args) {
while (true) {
leak.add(new byte[1024 * 1024]); // 1 MB
System.out.println("Stored objects: " + leak.size());
}
}
}
Why this leaks
leakis astaticliststaticfields usually live for the lifetime of the program- every loop adds a new 1 MB byte array
- the list keeps references to every array
- because the arrays are still referenced, the garbage collector cannot free them
Eventually, the program will likely throw:
Step by Step Execution
Consider this example:
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<String> items = new ArrayList<>();
items.add("A");
items.add("B");
items.add("C");
}
}
What happens step by step
itemsis created as a newArrayList.- The list object now exists on the heap.
"A","B", and"C"are added to the list.- The list stores references to those values.
- When
mainfinishes,itemsgoes out of scope. - If nothing else refers to that list, it becomes unreachable.
- The garbage collector can reclaim it later.
Now compare it with this leaking version:
import java.util.ArrayList;
java.util.List;
{
List<String> items = <>();
{
() {
items.add( ( + items.size()));
}
}
}
Real World Use Cases
Java memory leaks appear in real applications in patterns like these:
Unbounded caches
Map<String, Object> cache = new HashMap<>();
If entries are added forever and never evicted, memory keeps growing.
Event listeners not removed
A UI, messaging system, or framework may keep listener references. If you register listeners and never unregister them, old objects stay alive.
Static collections
private static final List<Session> sessions = new ArrayList<>();
If old sessions are stored forever, the application retains unnecessary objects.
ThreadLocal misuse
If values are placed in ThreadLocal and not cleared in long-running thread pools, those values can remain attached to threads much longer than expected.
Maps keyed by changing objects
Objects stored in maps may never be removed due to incorrect key handling or lifecycle management.
ORM or persistence context growth
In batch jobs, loading thousands of entities into a persistence context without clearing it can consume a large amount of memory.
Logging or request history buffers
Applications sometimes keep full request or error history in memory for debugging. If there is no limit, this becomes a leak pattern.
Real Codebase Usage
In real projects, developers usually do not write obviously leaking code on purpose. Instead, leaks come from design mistakes.
Common patterns that help prevent leaks
Bounded caches
Use size limits or time-based eviction.
// Conceptually: keep only a limited amount of data
A cache without limits is often just a memory leak with a nicer name.
Guarding object lifetime
Ask:
- How long should this data live?
- Who owns it?
- When should it be removed?
Remove listeners and callbacks
If you add a listener, make sure there is also a removal path.
Clear temporary collections in batch processing
In loops that process large amounts of data, avoid keeping all results in memory unless needed.
Use weak references when appropriate
For some use cases, WeakHashMap or weak references can help avoid retaining objects only because they are used as cache keys or metadata.
Validate cache strategy in code review
Developers often review:
- whether a cache has max size
- whether static fields are necessary
- whether background threads retain references
- whether objects are removed from registries
Related defensive patterns
- early cleanup after a task finishes
Common Mistakes
Mistake 1: Assuming garbage collection prevents all leaks
Java has garbage collection, but it only removes unreachable objects.
private static final List<Object> list = new ArrayList<>();
If list keeps growing, those objects are still reachable.
Mistake 2: Keeping everything in a static field
Broken pattern:
public class BadStore {
public static final List<String> DATA = new ArrayList<>();
}
Why it is risky:
- static data lives a long time
- many parts of the app can keep adding to it
- cleanup is often forgotten
Mistake 3: Building an unbounded cache
Broken code:
Map<String, String> cache = new HashMap<>();
public String getValue(String key) {
return cache.computeIfAbsent(key, k -> loadValue(k));
}
Comparisons
| Concept | What it means | Can GC fix it automatically? | Example |
|---|---|---|---|
| Java memory leak | Objects are still reachable but no longer useful | No | Growing static list |
| Normal memory use | Objects are still reachable and still needed | No, because they are valid | Active request data |
| Garbage | Objects are unreachable | Yes | Local object after method ends |
| OutOfMemoryError | JVM cannot allocate enough memory | Not always | Heap filled by retained objects |
Memory leak vs resource leak
| Type | Description | Example |
|---|
Cheat Sheet
Quick definition
A Java memory leak happens when objects are still reachable but no longer needed.
Core rule
- Reachable object -> not garbage-collected
- Unreachable object -> eligible for garbage collection
Common causes
staticcollections- unbounded caches
- listeners not removed
ThreadLocalvalues not cleared- long-lived maps and lists
- batch jobs retaining too much data
Classic leak example
private static final List<byte[]> leak = new ArrayList<>();
while (true) {
leak.add(new byte[1024 * 1024]);
}
Why it leaks
- the list is long-lived
- every array is still referenced
- GC cannot free referenced objects
Signs of a leak
- memory keeps growing over time
- GC runs more often
- application slows down
- heap dump shows many retained objects
OutOfMemoryError
FAQ
Can Java really have memory leaks if it has garbage collection?
Yes. Garbage collection only removes unreachable objects. If your program still references objects it no longer needs, they stay in memory.
What is the simplest Java memory leak example?
A static list that keeps growing forever is the classic example.
private static final List<Object> list = new ArrayList<>();
Is an OutOfMemoryError always caused by a memory leak?
No. It can also happen because the program legitimately needs more memory than the JVM heap allows.
Are caches memory leaks?
Not always. A bounded cache with eviction is usually fine. An unbounded cache can become a leak.
Why are static fields often involved in leaks?
Because they usually live for a long time, so anything they reference may also stay alive for a long time.
Can local variables cause memory leaks?
Usually not for long, because they go out of scope. But they can contribute temporarily if they hold large objects longer than needed.
How do developers find Java memory leaks?
They often use heap dumps, profilers, GC logs, and tools like VisualVM, Java Mission Control, or Eclipse MAT.
What is the interview-friendly definition of a Java memory leak?
A Java memory leak is when objects remain reachable and therefore cannot be garbage-collected, even though the program no longer needs them.
Mini Project
Description
Build a small Java program that demonstrates a memory leak by storing data in a long-lived static collection. This project helps you see that Java can still run out of memory when references are kept unnecessarily, even though garbage collection exists.
Goal
Create a program that repeatedly allocates memory, stores it in a static list, and eventually exhausts the heap because the objects remain reachable.
Requirements
- Create a Java class with a static collection that lives for the lifetime of the program.
- Repeatedly allocate new objects inside a loop.
- Store each new object in the collection instead of letting it be discarded.
- Print the number of stored objects as the program runs.
- Make the example simple enough to demonstrate why garbage collection cannot reclaim the objects.
Keep learning
Related questions
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Choosing a @NotNull Annotation in Java: Validation vs Static Analysis
Learn how Java @NotNull annotations differ, when to use each one, and how to choose between validation, IDE hints, and static analysis tools.
Convert a Java Stack Trace to a String
Learn how to convert a Java exception stack trace to a string using StringWriter and PrintWriter, with examples and common mistakes.