HOWTO · Java

How to Find Memory Leak in Java

This article explains how to find memory leak in Java.

On this page

A Java memory leak occurs when an application keeps objects reachable after they are no longer useful. The garbage collector reclaims unreachable objects, but it cannot reclaim an object that is still reachable through a live reference. As retained objects accumulate, they can increase memory use and eventually contribute to a java.lang.OutOfMemoryError. High memory use alone does not establish that a leak exists; investigate whether the live set grows under repeated, comparable work.

What Is a Java Memory Leak?

Java manages object memory automatically, so a leak usually means the program retains references longer than intended rather than failing to free memory manually. An object may be logically obsolete to the application while remaining reachable from a static field, a long-lived service, a collection, or another object. The garbage collector correctly preserves such reachable objects. This distinction helps separate unwanted retention from a temporary allocation spike or normal heap growth.

Common Causes of Java Memory Leaks

Look for ownership and lifecycle mistakes that let references outlive their purpose. Common examples include unbounded caches, session objects kept after a session ends, collections that continually receive entries without removing old ones, listeners that remain registered after their consumer is done, and custom data structures that retain obsolete nodes. These are investigation leads, not proof that any particular object is leaking. Check who owns each reference and when that owner should release it.

A Practical Workflow for Finding a Java Memory Leak

Start by reproducing the same workload in repeatable cycles. After each cycle has had an opportunity to complete and the garbage collector has run, record the live-memory baseline. Compare equivalent points rather than unrelated peaks during allocation. Compare post-GC live memory across repeated equivalent workload cycles; a rising baseline is a signal to investigate, not proof of a leak.

Diagram of equivalent workload cycles, rising post-GC live memory, heap-dump comparison, retained-reference tracing, a fix, and retesting.

Investigation loop for distinguishing a growing retained set from temporary allocation.

If the baseline keeps rising, capture heap evidence at comparable workload points. Compare object counts and retained sizes, trace suspicious objects to the references keeping them reachable, and identify the owner whose lifecycle is too long. Make a focused change, then repeat the same workload and compare the same measurements. A single sample can be misleading, so consider workload variation and normal application behavior before concluding that a leak is present.

Run this self-contained Java example to see how removing entries releases the collection’s references to them:

import java.util.HashMap;
import java.util.Map;

public class CacheLifecycle {
    public static void main(String[] args) {
        Map<Integer, byte[]> cache = new HashMap<>();
        for (int i = 0; i < 3; i++) {
            cache.put(i, new byte[1024]);
        }
        System.out.println("Entries before cleanup: " + cache.size());
        cache.clear();
        System.out.println("Entries after cleanup: " + cache.size());
    }
}

This small example shows collection ownership and cleanup; it does not measure heap usage or prove a production leak. In a real application, remove an entry only when its lifecycle or cache policy says it is no longer needed.

Analyze Heap Dumps and Retained Objects

A heap dump records objects and references at a point in time. Use it to find unusually numerous object types, inspect their retained sizes, and follow retaining paths toward long-lived roots such as static fields or application services. Compare dumps taken after equivalent workload cycles to see which populations grow and remain reachable. One dump identifies a state to investigate; it does not by itself prove that the state is a leak. Confirm the suspected retention against the application’s expected lifecycle.

Use Profilers and JVM Monitoring Tools

Monitoring can show whether heap use, allocation, or collection behavior changes over time. A profiler can help connect object populations and allocation activity to application behavior; heap-dump analysis can then reveal why instances remain reachable. Tools such as NetBeans Profiler, VisualVM, and JProfiler are examples found in Java profiling workflows, not recommendations about current availability or features. Choose a tool that fits the JVM and environment you are investigating, and treat its graphs and warnings as evidence to interpret rather than a diagnosis on their own.

Inspect Garbage Collection Logs

The JVM option -verbose:gc is a source example for enabling verbose garbage-collection logging. Its applicability and the format and interpretation of the resulting log depend on the JVM version. Review collection behavior and post-collection memory over comparable workload intervals. A log can help show that memory remains live after collection, but a summary alone does not identify which references retain objects or establish the cause.

Understand Eclipse Memory Leak Warnings and Their Limits

Eclipse warnings can draw attention to certain resource or reference patterns in code. Read each warning in context and verify the relevant object and resource lifecycle. An IDE warning is a partial signal: it cannot establish that every warning is a memory leak, nor can the absence of a warning establish that an application has no leak. Do not rely on the old JDK 1.5 compatibility claim as a general description of current detection behavior.

Verify the Fix and Reduce Retained Memory

After changing the suspected owner or cleanup path, repeat the same workload and compare post-GC live memory, object counts, and retained paths at equivalent points. Check that expected objects remain available while obsolete objects are no longer retained. If the baseline still grows, inspect the new evidence and continue tracing references. Report performance improvements only when measurements under comparable conditions support them; the goal is to verify the retention change, not to assume every memory reduction makes the application faster.