How to Find Memory Leaks in .NET Applications

How to find memory leaks in .NET Core applications using dotnet-dump and dotnet-counters tools
Common tools and techniques used to detect memory leaks in .NET Core applications.

Modern .NET and ASP.NET Core applications are designed with automatic memory management using the Garbage Collector (GC). This feature significantly reduces manual memory handling compared to languages like C or C++.

However, memory leaks can still occur in .NET applications when objects remain referenced unintentionally. Over time, these leaks can cause high memory usage, degraded performance, slow APIs, and even application crashes.

Memory leaks are especially dangerous in long-running applications such as:

  • ASP.NET Core Web APIs
  • Background workers
  • Microservices
  • Windows services
  • Cloud-hosted applications

In this guide, you will learn:

  • What memory leaks are in .NET Core
  • Common causes of memory leaks
  • How to detect memory leaks
  • Tools used by professionals
  • Practical code examples to prevent memory leaks

This guide focuses on real-world production troubleshooting techniques used by experienced .NET developers.


What is a Memory Leak in .NET?

A memory leak happens when an application allocates memory but does not release it when it is no longer needed.

In .NET, the Garbage Collector (GC) automatically frees unused objects. However, if objects remain referenced in memory, the GC cannot remove them.

This results in:

  • Continuous memory growth
  • Higher CPU usage due to frequent GC cycles
  • Reduced application performance
  • Possible application crashes

Example symptoms include:

  • ASP.NET Core API memory continuously increasing
  • Containers restarting due to memory limits
  • Slow response times under heavy load

Why Memory Leaks Still Occur in .NET Core

Even with automatic memory management, developers can accidentally create situations where objects cannot be collected.

Common causes include:

  • Unreleased event subscriptions
  • Static object references
  • Improper caching
  • Not disposing unmanaged resources
  • Long-lived services holding references
  • Background tasks retaining objects

Understanding these patterns is the first step toward solving memory problems.


How Garbage Collection Works in .NET

The .NET Garbage Collector manages memory using a generational model.

Objects are categorized into three generations.

Generation 0

Contains short-lived objects.

Example:

  • Temporary variables
  • Request objects

These objects are collected frequently.


Generation 1

Objects that survive Generation 0 collections move here.


Generation 2

Long-lived objects end up in Generation 2.

Examples include:

  • Cached data
  • Static objects
  • Application-level services

Memory leaks usually appear as growing Generation 2 memory usage.


Common Causes of Memory Leaks in .NET Core Applications

Let’s explore real coding scenarios that cause memory leaks.


1 Event Handler Memory Leaks

One of the most common memory leak scenarios occurs when objects subscribe to events but never unsubscribe.

Example of a problematic implementation:

public class OrderService
{
public OrderService()
{
OrderProcessor.OrderCompleted += HandleOrderCompleted;
} private void HandleOrderCompleted(object sender, EventArgs e)
{
Console.WriteLine("Order completed");
}
}

If this service is created multiple times, each instance remains referenced by the event handler.

The garbage collector cannot release it.


Fix: Always Unsubscribe from Events

public class OrderService : IDisposable
{
public OrderService()
{
OrderProcessor.OrderCompleted += HandleOrderCompleted;
} private void HandleOrderCompleted(object sender, EventArgs e)
{
Console.WriteLine("Order completed");
} public void Dispose()
{
OrderProcessor.OrderCompleted -= HandleOrderCompleted;
}
}

Using IDisposable ensures proper cleanup.


2 Static Objects Holding References

Static objects live for the entire lifetime of the application.

If they reference large objects, memory may never be released.

Example:

public static class CacheManager
{
public static List<Customer> Customers = new List<Customer>();
}

If this list keeps growing, the memory will never be released.


Fix: Use MemoryCache with Expiration

Modern .NET applications should use IMemoryCache.

Example:

public class CustomerService
{
private readonly IMemoryCache _cache; public CustomerService(IMemoryCache cache)
{
_cache = cache;
} public List<Customer> GetCustomers()
{
return _cache.GetOrCreate("customers", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
return LoadCustomersFromDatabase();
});
}
}

This ensures the cache expires and memory is released.


3 Not Disposing Unmanaged Resources

Objects such as:

  • File streams
  • Database connections
  • HTTP clients
  • Network streams

use unmanaged resources and must be disposed.

Bad example:

public void ReadFile()
{
FileStream fs = new FileStream("data.txt", FileMode.Open);
}

This file handle remains open.


Correct Implementation Using using

public void ReadFile()
{
using FileStream fs = new FileStream("data.txt", FileMode.Open);
}

The using statement automatically disposes resources.


4 Long-Lived Background Services

Background services may unintentionally store objects in memory.

Example:

public class DataCollector
{
private static List<string> _logs = new(); public void AddLog(string log)
{
_logs.Add(log);
}
}

If logs keep increasing, memory will grow indefinitely.


Fix: Limit Stored Data

public void AddLog(string log)
{
if (_logs.Count > 1000)
_logs.RemoveAt(0); _logs.Add(log);
}

Or use a logging framework instead of storing logs manually.


Tools to Detect Memory Leaks in .NET Applications

Professional developers rely on specialized tools to analyze memory usage.


1 dotnet-counters

The dotnet-counters tool monitors runtime metrics.

Run the following command:

dotnet-counters monitor -p <process-id>

Important metrics include:

  • GC Heap Size
  • Allocation Rate
  • Gen 2 GC Count

If heap size continuously increases, it may indicate a memory leak.


2 dotnet-dump

This tool captures memory snapshots of running applications.

Command:

dotnet-dump collect -p <process-id>

You can then analyze the dump using:

dotnet-dump analyze

This helps identify objects consuming the most memory.


3 Visual Studio Diagnostic Tools

Visual Studio provides built-in profiling tools.

Steps:

1 Open Debug → Performance Profiler

2 Select Memory Usage

3 Run the application

4 Capture memory snapshots

You can compare snapshots to identify growing objects.


4 JetBrains dotMemory

JetBrains dotMemory is one of the most powerful .NET memory profiling tools.

It helps developers:

  • Identify object retention paths
  • Detect memory leaks
  • Analyze GC behavior

Monitoring Memory in Production

In production environments, monitoring tools are essential.

Popular monitoring platforms include:

  • Azure Application Insights
  • Prometheus
  • Grafana
  • Datadog

These tools help track:

  • Heap size
  • GC activity
  • Memory usage trends

If memory steadily increases over time, a memory leak may exist.


Best Practices to Prevent Memory Leaks

Follow these practices to keep your .NET applications healthy.

Always Dispose Resources

Use using or implement IDisposable.


Avoid Static Memory Storage

Do not store large collections in static variables.


Use Proper Caching Strategies

Use IMemoryCache or Redis instead of custom caching.


Unsubscribe from Events

Always remove event handlers when objects are disposed.


Monitor Production Systems

Track memory usage regularly using monitoring tools.


Real-World Example of Memory Leak Detection

Imagine a high-traffic ASP.NET Core API that processes thousands of requests per minute.

After deployment, engineers notice:

  • API memory increases continuously
  • Containers restart frequently

Using dotnet-counters, they detect:

GC Heap Size increasing constantly

Using dotnet-dump, they identify a large number of objects held by an event handler.

After fixing the event subscription issue, memory stabilizes and the application runs normally.

This demonstrates the importance of monitoring and proper memory management.


Frequently Asked Questions (FAQ)

What is the most common cause of memory leaks in .NET?

The most common causes include unreleased event handlers, static object references, improper caching, and unmanaged resources not being disposed properly.


Does .NET automatically prevent memory leaks?

No. While the Garbage Collector manages memory automatically, developers must ensure objects are no longer referenced.


How can I check memory usage in a .NET application?

You can use tools such as:

  • dotnet-counters
  • dotnet-dump
  • Visual Studio Profiler
  • JetBrains dotMemory

These tools help analyze memory allocations and object references.


What is the Large Object Heap (LOH)?

The Large Object Heap stores objects larger than 85KB.

Frequent allocations in LOH can cause fragmentation and reduce application performance.


Can ASP.NET Core applications have memory leaks?

Yes. ASP.NET Core applications can suffer from memory leaks if objects are referenced unintentionally, especially in caching, background services, and event subscriptions.


Conclusion

Memory leaks in .NET applications can silently degrade system performance and lead to serious production issues. Even though .NET provides automatic memory management, developers must still follow best practices to ensure objects are released properly.

By understanding how the Garbage Collector works, identifying common leak patterns, and using tools such as dotnet-counters, dotnet-dump, and Visual Studio Profiler, developers can quickly detect and fix memory problems.

Regular monitoring, proper resource disposal, and careful architecture decisions will help keep your ASP.NET Core applications stable, scalable, and efficient.

More Important Articles

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top