How to Find, Fix, and Prevent Memory Leaks
Learn how to confirm a memory leak, identify the growing memory domain, trace what is retained, and verify a fix with repeatable Java and Windows workflows.
A rising memory graph is a clue, not proof of a leak. To find one, reproduce the growth under a stable workload, measure the relevant memory pool or resource after cleanup, identify what remains retained, fix its ownership or lifecycle, and repeat the same workload to confirm the growth stops.
The tools and memory models differ by platform. The concrete procedures below are scoped to Java and Windows, the platforms covered by the primary sources cited here. Do not apply Java heap or Windows kernel guidance to another runtime without its own documentation.
1. Confirm that memory keeps growing
Start by recording a baseline after startup has settled. Note the workload, elapsed time, memory counters, and any relevant resource counts. Repeat the same workload and observations under comparable conditions. A short-lived increase during allocation may be normal; persistent growth across comparable cycles is more informative.
For Java, compare the live set after full garbage collections: the Java heap or Metaspace still in use after collection. Oracle identifies a rising live set under stable conditions as a strong indication of a leak. Process memory alone is less specific because it can include native memory and other runtime costs. Oracle Java SE 26: Troubleshoot Memory Leaks.
For Windows, begin with Performance Monitor to establish whether a leak exists, then monitor counters relevant to the suspected resource. Microsoft calls out Commit Size, Handles, User Objects, and GDI Objects as useful observations for setting an application baseline. A single generic memory number can hide which resource is growing. Microsoft Learn: Find and Fix Memory Leaks in Windows and Microsoft Learn: Preventing Memory Leaks in Windows Applications.
2. Identify the memory domain
Before choosing a tool or changing a limit, determine what is actually exhausted or growing. Similar symptoms can have different causes and require different diagnostics.
| Evidence | What to investigate |
|---|---|
| Java heap usage remains high after full GC | Live Java objects and their references; inspect heap evidence and retention paths. |
| Java Metaspace usage rises or the error names Metaspace | Metaspace separately from the Java heap; a heap-only analysis will not answer this diagnosis. |
| Process memory rises but Java heap live set does not | Native memory or other process allocations, using operating-system tools appropriate to the platform. |
| Windows Commit Size, Handles, User Objects, or GDI Objects grows | The corresponding user-mode allocation or resource lifecycle. |
| Windows evidence points to a kernel-mode component | Follow the kernel-mode investigation path, distinct from user-mode application debugging. |
An OutOfMemoryError does not by itself prove a leak. Oracle lists undersized heap or Metaspace, native-memory exhaustion, a large allocation, and excessive finalization among possible causes. Use the error details and pool evidence to determine which case applies before selecting a fix.
3. Collect evidence while the growth occurs
Java: record, inspect, and narrow the classes
- Capture a Java Flight Recorder recording that covers the period when the growth occurs. JDK Mission Control and JFR are a documented route for inspecting runtime behavior.
- Compare live-object trends and allocation stacks. Look for object types that continue to survive comparable workload cycles.
- Inspect paths to GC roots to learn which references keep suspect objects reachable. Oracle cautions that collecting GC-root paths can add overhead comparable to a full garbage collection, so account for that when choosing when to collect the evidence.
- Use a heap histogram to quickly narrow down which classes deserve closer investigation. A histogram narrows the search; it does not alone prove why objects remain reachable.
Oracle also names Eclipse Memory Analyzer and YourKit as examples of third-party tools. The cited documentation does not provide a comparative benchmark or endorse a winner. Compare diagnostic options by the memory domains they expose, whether they show live-set trends and allocation stacks, whether they reveal retention paths, collection overhead, production suitability, and compatibility with the deployed runtime. See Oracle Java SE 24: Troubleshoot Memory Leaks for the documented heap diagnostic context.
Native memory: use platform-appropriate measurements
If the Java heap evidence does not explain process growth, investigate native allocations with operating-system tools suitable for the target platform. Compare repeated measurements to locate regions that grow. Oracle notes there is no single ideal native-code leak diagnostic for every platform, so select a tool based on the runtime and operating system rather than assuming heap tooling covers native allocations.
Windows: follow the resource type and execution mode
Use Performance Monitor to establish the trend and collect counters that correspond to the observed resource. Then follow the relevant Microsoft investigation path for user-mode or kernel-mode components. Heap allocations, virtual allocations, kernel handles, and GDI or USER handles have distinct lifecycles; evidence about one does not diagnose the others.
4. Trace what is retaining or failing to release resources
Compare repeated workload cycles and ask which object counts or resource counts keep climbing. In Java, follow allocation stacks and paths to GC roots from surviving objects back to the code that still references them. Oracle gives an example of objects entering a HashSet through a login flow and suggests checking whether entries are removed when a user logs out. It is an illustration of a possible retention path, not a universal explanation.
In native or Windows code, inspect allocation and release pairs in the subsystem implicated by the measurements. Check whether every acquired allocation, handle, or graphics/user resource is released on normal completion and on error or cancellation paths. Microsoft identifies heap memory, virtual memory, kernel handles, and GDI/USER handles as distinct resource types that can leak when not released.
5. Fix the lifecycle defect and verify the result
- Use the evidence to identify the owner responsible for retaining an object or releasing a resource.
- Correct the reference or lifecycle defect in that owner. Make sure cleanup also runs on failure, cancellation, and early-return paths relevant to the code.
- Repeat the representative workload with the same observation window and counters used for the baseline.
- For Java, compare post-full-GC live sets. For Windows, compare the same resource counters. Record whether growth stops across repeated comparable cycles.
If the evidence instead shows that a pool is smaller than the application’s legitimate working set, review capacity separately. Increasing a heap or resource limit can postpone failure when capacity is genuinely insufficient, but it does not correct unintended retention. A restart or a larger limit alone does not demonstrate that a leak is fixed.
Common errors and how to troubleshoot them
| Symptom or mistake | Likely cause | Next step |
|---|---|---|
| Process memory rose once, so a leak is declared | Startup, workload changes, transient allocations, or non-heap memory may explain the rise. | Repeat under stable load and compare relevant counters after cleanup or full GC where applicable. |
OutOfMemoryError is treated as proof of a Java heap leak |
The exhausted domain may be Metaspace or native memory; sizing, a large allocation, or excessive finalization may also be involved. | Read the actual error and identify the failing pool or allocation before investigating. |
| Heap analysis does not explain process growth | The growth may be in native memory rather than Java objects. | Measure native memory with tools appropriate to the operating system and runtime. |
| A histogram identifies a class, but no cause is apparent | A class count shows what is present, not which reference retains it. | Inspect allocation evidence and reference paths to GC roots. |
| GC-root collection changes timing or causes extra overhead | Oracle notes this collection can add overhead comparable to a full GC. | Plan collection during a suitable diagnostic window and interpret it with the recording context. |
| Windows memory appears stable but a resource leak continues | Handles, User Objects, or GDI Objects may grow without a matching change in a generic memory measure. | Monitor the relevant resource counters and choose the user-mode or kernel-mode path based on evidence. |
| The issue disappears after restart or a limit increase | Restart resets process state; a larger limit may only delay exhaustion. | Reproduce the same workload after the code change and compare the original counters over time. |
Performance, reliability, and cost considerations
- Keep the workload comparable. A changed request mix or traffic level makes before-and-after memory trends difficult to interpret.
- Choose evidence with overhead in mind. Java recordings and heap diagnostics provide different detail; GC-root path collection can add overhead comparable to a full GC.
- Measure the domain that matters. Heap, Metaspace, native memory, Windows commit, and handle/object counts are not interchangeable.
- Separate diagnosis from capacity planning. Add capacity only when measurements show the legitimate working set needs it; do not use it as evidence that retention is corrected.
- Tool cost is a separate decision. Oracle names JDK Mission Control/JFR and examples of third-party analyzers, but the cited sources give no tool price comparison or benchmark. Confirm tool compatibility and licensing for the deployed environment.
Or skip the browser setup
When investigating a memory issue, a screenshot of a dashboard or status page can make a visual record of what the team sees at a given point. If you need a clean capture without maintaining browser automation, ScreenshotNeo is a website screenshot API and MCP server for developers. It returns a PNG, JPEG, WebP, or PDF from one GET request. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
// Save bytes using your Node.js application's file or storage API.
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
FAQ
How do I find a memory leak?
Reproduce it under stable load, measure the relevant memory domain across comparable cycles, and determine whether growth persists after cleanup. Then use diagnostics suited to that platform to identify what remains retained.
How can I tell if my application has a memory leak?
Look for sustained growth under a stable workload after transient activity settles. In Java, a rising live set after full garbage collection is stronger evidence than process memory growth alone.
What causes an OutOfMemoryError?
Possible causes include a leak, undersized heap or Metaspace, native-memory exhaustion, a large allocation, or excessive finalization. Identify the failed pool or allocation first.
How do I fix a memory leak?
Correct the ownership, reference, or release lifecycle implicated by diagnostics, then repeat the same workload and compare the same counters to verify that growth stops.
Does every platform use Java heap dumps or Windows Performance Monitor?
No. These are platform-specific procedures. Use diagnostics and memory-domain definitions documented for the runtime and operating system you deploy.


