Fundamentals 10 min read

Common Causes of Java Memory Leaks and How to Fix Them

This article explains the typical sources of Java OutOfMemoryError—including static collections, unclosed connections, improper variable scopes, inner‑class references, hash value changes, cache misuse, and lingering listeners—and provides code examples and solutions to prevent memory leaks.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Common Causes of Java Memory Leaks and How to Fix Them

OOM (Out Of Memory) is a Java error thrown when the JVM cannot allocate an object because both the heap and the garbage collector have no free space.

1. Static collection classes

Static containers such as HashMap or LinkedList live as long as the program, preventing contained objects from being reclaimed and causing memory leaks.

2. Various connections (database, network, I/O)

Failing to close connections (Connection, Statement, ResultSet) leaves objects unreclaimed, leading to leaks.

3. Improper variable scope

Variables whose declared scope exceeds their usage keep references alive; not setting objects to null can also cause leaks.

public class UsingRandom {
    private String msg;

    public void receiveMsg() {
        readFromNet(); // receive data from network into msg
        saveDB(); // save msg to database
    }
}

The above pseudo‑code keeps msg as a field, so it cannot be collected after use.

Moving msg inside the method or setting it to null after saving eliminates the leak.

4. Inner class holding outer class

If an inner‑class instance is retained, it also retains a reference to its outer class, preventing the outer instance from being garbage‑collected.

5. Changing hash values

Modifying fields that participate in hashCode after an object is stored in a HashSet makes it impossible to locate or remove the object, effectively leaking it.

6. Example – find the leak

import java.util.Arrays;

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

6.1 Cause analysis

The program leaks memory because pop does not clear the reference to the popped element; the array still holds it.

6.2 Fix

public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;
    return result;
}

Clearing the stale reference allows the GC to reclaim the object.

7. Cache leakage

Storing objects in a cache without proper eviction can cause leaks; using WeakHashMap lets entries be removed automatically when keys are no longer referenced.

7.1 Code example

package com.ratel.test;

import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;

public class MapTest {
    static Map wMap = new WeakHashMap();
    static Map map = new HashMap();

    public static void main(String[] args) {
        init();
        testWeakHashMap();
        testHashMap();
    }

    public static void init() {
        String ref1 = new String("object1");
        String ref2 = new String("object2");
        String ref3 = new String("object3");
        String ref4 = new String("object4");
        wMap.put(ref1, "cacheObject1");
        wMap.put(ref2, "cacheObject2");
        map.put(ref3, "cacheObject3");
        map.put(ref4, "cacheObject4");
        System.out.println("String references ref1‑ref4 disappear");
    }

    public static void testWeakHashMap() {
        System.out.println("WeakHashMap before GC");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("WeakHashMap after GC");
        for (Object o : wMap.entrySet()) {
            System.out.println(o);
        }
    }

    public static void testHashMap() {
        System.out.println("HashMap before GC");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
        try {
            System.gc();
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HashMap after GC");
        for (Object o : map.entrySet()) {
            System.out.println(o);
        }
    }
}

The demo shows that after the local string variables go out of scope, the WeakHashMap entries are reclaimed by GC, while the regular HashMap entries remain.

8. Listeners and callbacks

Registered listeners that are never deregistered also retain references and cause leaks; storing them as keys in a WeakHashMap or explicitly removing them prevents the problem.

Javagarbage collectionbest practicesmemory leakOutOfMemoryErrorWeakHashMap
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.