Memory Leaks
.NET
Debugging Tools
Software Development
Performance Optimization

What strategies and tools are useful for finding memory leaks in .NET?

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

Introduction

In .NET, a memory leak usually does not mean “memory was allocated and never freed” in the C sense. More often, it means objects are still reachable, so the garbage collector correctly refuses to reclaim them even though the application no longer intends to use them.

Start by Defining the Leak Pattern

Before opening a profiler, decide what kind of memory growth you are seeing.

Common cases include:

  • managed objects retained by event handlers or caches
  • large object heap growth from arrays, strings, or buffers
  • unmanaged memory growth through native interop or image libraries
  • pinned objects or IDisposable misuse

This distinction matters because the right tool and the right fix depend on which memory space is actually growing.

The First Diagnostic Step: Reproduce and Snapshot

A good leak investigation starts with a repeatable scenario.

  1. start the app in a known baseline state
  2. perform the same action repeatedly
  3. observe whether memory returns near baseline after GC-friendly idle time
  4. capture snapshots before and after the leak pattern

If you cannot reproduce the growth consistently, profiling tends to generate more noise than insight.

Tools That Are Commonly Useful

Visual Studio Memory Profiler

Visual Studio’s diagnostics tools are a good first stop for many .NET applications. You can capture memory snapshots, compare object counts, and inspect retention paths.

It is especially useful when you need a quick answer to “which object types keep growing?”

dotnet-dump and dotnet-gcdump

For production-like or remote environments, command-line tools are often easier than attaching a full GUI profiler.

Examples:

bash
dotnet-gcdump collect -p 12345
dotnet-dump collect -p 12345

These let you capture the state of a running process and analyze it later.

PerfView

PerfView is very powerful for GC and allocation analysis. It is not the friendliest tool at first glance, but it is excellent for investigating:

  • allocation hot paths
  • GC pressure
  • retained managed memory
  • heap diffs over time

dotMemory or Similar Commercial Profilers

JetBrains dotMemory and similar profilers make retention analysis and snapshot comparison more approachable. They are particularly helpful for finding the “why is this object still alive” question quickly.

Look for Retention Paths, Not Just Big Types

Finding a large number of MyViewModel instances is useful, but not enough. The real question is what is keeping them alive.

Typical retention roots include:

  • static fields
  • singleton services
  • event subscriptions
  • timers
  • task continuations
  • caches with no eviction

If the profiler shows the retention path, follow it all the way to the root instead of stopping at the object type.

Classic Leak Example: Events

A very common managed leak is a long-lived publisher holding on to short-lived subscribers.

csharp
1using System;
2
3class Publisher
4{
5    public event EventHandler? Changed;
6    public void Raise() => Changed?.Invoke(this, EventArgs.Empty);
7}
8
9class Subscriber
10{
11    private readonly Publisher _publisher;
12
13    public Subscriber(Publisher publisher)
14    {
15        _publisher = publisher;
16        _publisher.Changed += OnChanged;
17    }
18
19    private void OnChanged(object? sender, EventArgs e) { }
20
21    public void Dispose()
22    {
23        _publisher.Changed -= OnChanged;
24    }
25}

If Dispose() is never called, the publisher may keep subscribers alive indefinitely.

Don’t Ignore Unmanaged Memory

Some .NET applications leak outside the managed heap through native libraries, bitmaps, sockets, or unmanaged handles. In those cases, GC snapshots alone can be misleading because the managed heap may look reasonable while process memory keeps rising.

That is where handle counters, native diagnostics, and careful IDisposable review become important.

A leak involving Bitmap, streams, database handles, or interop wrappers often turns out to be a disposal problem rather than a purely managed retention problem.

Common Pitfalls

The most common mistake is assuming rising process memory automatically means a managed leak. It may be unmanaged memory, caching, or normal GC behavior under load.

Another issue is collecting one snapshot without a comparison point. Leak analysis works much better when you compare before and after a reproducible scenario.

People also focus on allocation rate instead of retention. High allocation is not a leak if objects die normally.

Finally, do not trust forced GC as proof that everything is fine. If objects are still rooted, GC will correctly keep them alive.

Summary

  • In .NET, leaks usually mean objects remain reachable, not that the GC failed.
  • Reproduce the memory growth first, then compare snapshots around the same workload.
  • Use tools such as Visual Studio diagnostics, dotnet-dump, dotnet-gcdump, PerfView, or dotMemory.
  • Follow retention paths to the root cause instead of stopping at object counts.
  • Always consider unmanaged memory and disposal mistakes, not just the managed heap.

Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms