Fundamentals 14 min read

Why Overriding equals Without hashCode Can Cause Memory Leaks in Java

This article explores the distinction between OutOfMemory errors and memory leaks in Java, explains how improper use of static fields, unclosed streams, incorrect equals/hashCode implementations, and ThreadLocal can lead to leaks, and provides practical solutions and tools such as JVisualVM to detect and prevent them.

Programmer DD
Programmer DD
Programmer DD
Why Overriding equals Without hashCode Can Cause Memory Leaks in Java

Origin

The discussion started when a group member asked: "What happens if you override equals but not hashCode?"

If you override equals without overriding hashcode what impact?

The question led to a deep dive into memory overflow and memory leak.

Memory Overflow vs Memory Leak

Out of Memory (OOM) occurs when the heap runs out of space. Memory leak means objects that are no longer used cannot be reclaimed by the garbage collector.

What is a memory leak?

Memory leak is when the heap contains objects that are no longer referenced by the program but the GC cannot collect them because they are still reachable.

When many such objects accumulate, OOM may follow.

Problems caused by memory leaks

Performance degradation over long‑running applications.

Unexpected crashes.

Exhaustion of connection objects.

Eventually OOM.

Common scenarios that cause memory leaks and solutions

Misuse of static member variables

Example:

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

Solution: keep the list inside the method so it is reclaimed after the stack frame exits.

public void populateList() {
    List<Double> list = new ArrayList<>();
    for (int i = 0; i < 10000000; i++) {
        list.add(Math.random());
    }
}

Unclosed streams

Always close streams, preferably with try‑with‑resources or finally.

@Cleanup InputStream jobJarInputStream = new URL(jobJarUrl).openStream();
@Cleanup OutputStream jobJarOutputStream = new FileOutputStream(jobJarFile);
IOUtils.copy(jobJarInputStream, jobJarOutputStream);

Incorrect equals and hashCode implementations

When hashCode is not overridden, the default Object hashCode is used, causing duplicate keys in collections and potential leaks.

public class MemLeakTest {
    public static void main(String[] args) throws InterruptedException {
        Map<Person, String> map = new HashMap<>();
        Person p1 = new Person("zhangsan", 1);
        Person p2 = new Person("zhangsan", 1);
        Person p3 = new Person("zhangsan", 1);
        map.put(p1, "zhangsan");
        map.put(p2, "zhangsan");
        map.put(p3, "zhangsan");
        System.out.println(map.entrySet().size()); // 3
    }
}
@Getter @Setter
class Person {
    private String name;
    private Integer id;
    public Person(String name, Integer id) {
        this.name = name;
        this.id = id;
    }
}

Changing an object's fields that affect hashCode after it is stored in a HashSet can make removal fail, leading to leaks.

Set<Person> set = new HashSet<>();
Person p3 = new Person("wanger", 3);
set.add(p3);
p3.setName("wangermao");
set.remove(p3); // fails because hashCode changed
set.add(p3); // adds a duplicate

ThreadLocal misuse

ThreadLocal variables that are not removed keep references in worker threads, preventing GC.

try {
    threadLocal.set(System.nanoTime());
    // business code
} finally {
    threadLocal.remove();
}

JVisualVM

JVisualVM can analyze heap, CPU, threads, and GC behavior of local or remote JVMs.

How hashCode is generated

OpenJDK uses several strategies; in JDK 1.8 it uses an Xorshift‑based algorithm (strategy 5). Earlier versions used random numbers.

HashCode and biased locking conflict

Calling Object.hashCode() or System.identityHashCode() disables biased locking for that object. To keep biased locking, override hashCode.

Conclusion

The article clarified the difference between OOM and memory leaks, listed common leak sources, and offered remedies such as proper static field handling, closing streams, correct equals/hashCode, and ThreadLocal cleanup. Understanding these details helps avoid subtle bugs that can crash applications.

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.

JVMmemory leakThreadLocalOutOfMemoryhashcode
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.