Fundamentals 16 min read

How to Detect and Fix Java Memory Leaks: Real‑World Examples and Best Practices

This tutorial explains what Java memory leaks are, why they happen despite garbage collection, shows common leak patterns such as static fields, unclosed resources, improper equals/hashCode, inner classes, finalize, constant strings and ThreadLocal, and provides practical detection and prevention techniques.

Programmer DD
Programmer DD
Programmer DD
How to Detect and Fix Java Memory Leaks: Real‑World Examples and Best Practices

1. Introduction

One of Java's core strengths is its built‑in garbage collector (GC) that automatically manages memory, but GC is not a silver bullet; memory leaks can still occur and eventually exhaust heap space.

2. What Is a Memory Leak?

A memory leak happens when objects that are no longer used remain reachable in the heap, preventing the GC from reclaiming them. Leaks waste memory, degrade performance, and can cause java.lang.OutOfMemoryError.

3. Common Java Memory‑Leak Sources

3.1 Static Fields

Static variables live as long as the application does. Storing large collections in static fields can keep them in memory forever.

public class StaticTest {
    public static List<Double> list = new ArrayList<>();
    public void populateList() {
        for (int i = 0; i < 10_000_000; i++) {
            list.add(Math.random());
        }
    }
    public static void main(String[] args) {
        new StaticTest().populateList();
    }
}

3.2 Unclosed Resources

Failing to close streams, database connections, or sessions leaves them allocated, preventing GC and possibly triggering OutOfMemoryError. Always close resources in a finally block or use try‑with‑resources.

3.3 Incorrect equals() / hashCode() Implementations

When objects are used as keys in HashMap or stored in HashSet, failing to override equals and hashCode correctly can cause duplicate entries to accumulate.

public class Person {
    private String name;
    public Person(String name) { this.name = name; }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person p = (Person) o;
        return name.equals(p.name);
    }
    @Override
    public int hashCode() { return 31 * 17 + name.hashCode(); }
}

3.4 Non‑static Inner Classes

Non‑static inner classes hold an implicit reference to their outer instance, preventing the outer object from being collected even after it goes out of scope. Declaring the inner class as static removes this hidden reference.

3.5 Overridden finalize()

Objects with a custom finalize method are placed on a finalizer queue and are not reclaimed immediately, which can delay GC and cause leaks. Avoid using finalizers.

3.6 Constant Strings

Calling String.intern() on large strings in Java 6 and earlier stores them in the PermGen space, which never gets reclaimed until the JVM exits. Upgrade to Java 7+ or avoid interning large strings.

3.7 ThreadLocal

ThreadLocal variables retain a reference to their values for the lifetime of the thread. In thread‑pooled environments (e.g., servlet containers), failing to call remove() can keep objects alive after the request finishes.

4. Additional Leak‑Mitigation Strategies

4.1 Profiling Tools

Use profilers (VisualVM, JProfiler, YourKit, etc.) to monitor heap usage, identify hot allocation paths, and locate leaked objects.

4.2 Detailed GC Logging

Enable verbose GC flags (e.g., -verbose:gc -XX:+PrintGCDetails) to see when collections occur and how much memory is reclaimed.

4.3 Reference Objects

Java's java.lang.ref package (SoftReference, WeakReference, PhantomReference) can be used to hold caches that allow the GC to reclaim entries when memory is low.

4.4 IDE Warnings

Eclipse (JDK 1.5+) can flag obvious leak patterns; regularly check the Problems view for such warnings.

4.5 Benchmarking

Micro‑benchmarks help compare alternative implementations and choose the most memory‑efficient approach.

4.6 Code Review

Manual reviews often catch simple mistakes—unused static collections, missing close() calls, or forgotten ThreadLocal.remove() —that automated tools miss.

5. Conclusion

Memory leaks degrade performance and can crash Java applications. There is no single cure; they arise from many patterns. By following best practices, using profiling tools, and performing regular code reviews, you can dramatically reduce the risk of leaks.

Memory leak illustration
Memory leak illustration
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

memory leakProfilingbest-practicesgarbage-collection
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.