If your web application is slowing down, randomly restarting, or crashing with OutOfMemoryException errors, you are likely dealing with a memory issue. High memory usage in ASP.NET Core applications is a common challenge that can severely impact user experience, inflate cloud hosting costs, and degrade server stability.
Modern ASP.NET Core is highly optimized and incredibly fast, but it is not immune to memory leaks or inefficient allocations. Diagnosing these issues requires a systematic approach, the right diagnostic tools, and a solid understanding of how the .NET runtime manages memory.
This comprehensive guide will walk you through the root causes of high memory usage in ASP.NET Core, the tools you need to track it down, and actionable steps to resolve the bottleneck and keep your application running smoothly.
1. Understanding .NET Memory Management
Before diving into diagnostics, it is crucial to understand how ASP.NET Core handles memory. The .NET framework utilizes a Garbage Collector (GC) to automatically allocate and release memory for your application.
The GC divides the managed heap into three distinct generations to optimize performance:
-
Generation 0 (Gen 0): This is where short-lived objects (like temporary variables inside a method) are allocated. The GC cleans this generation very frequently.
-
Generation 1 (Gen 1): This acts as a buffer. Objects that survive a Gen 0 garbage collection are promoted to Gen 1.
-
Generation 2 (Gen 2): This generation houses long-lived objects (like static variables or application-wide singletons). Collecting Gen 2 is resource-intensive and can cause application pauses.
-
Large Object Heap (LOH): Any object larger than 85,000 bytes is allocated directly to the LOH. The LOH is collected alongside Gen 2 and is prone to fragmentation, which can artificially inflate memory usage.
When an ASP.NET Core application experiences “high memory,” it usually means that objects are accumulating in Generation 2 or the LOH faster than the Garbage Collector can clean them up.

2. Common Causes of High Memory Usage
Memory issues typically fall into two categories: Memory Leaks (memory that is never released) and Inefficient Allocations (allocating too much memory too quickly). Here are the most frequent culprits in ASP.NET Core:
Event Handlers and Static References
If an object subscribes to an event but never unsubscribes, the event publisher holds a strong reference to that object, preventing the GC from collecting it. Similarly, caching large datasets in static fields or Singleton services without expiration policies will keep that memory locked for the lifetime of the application.
Unbounded Caching
Using IMemoryCache is a great way to improve performance, but failing to set absolute or sliding expirations—or failing to limit the maximum size of the cache—can result in the application consuming all available server RAM.
Improper HttpClient Usage
A classic mistake in .NET is instantiating a new HttpClient for every outgoing request. Under heavy load, this not only exhausts socket connections but also creates excessive memory overhead.
Large Object Allocations
Reading massive files completely into memory (like a large CSV or image) creates massive arrays that go straight to the LOH. If this happens concurrently across multiple HTTP requests, the server’s memory will spike dramatically.
Un-disposed Unmanaged Resources
While the GC handles managed memory, it does not automatically close unmanaged resources like file streams, database connections, or network sockets. Failing to call .Dispose() or failing to use using statements on objects that implement IDisposable leads to severe memory and resource leaks.
3. The Diagnostic Toolkit for ASP.NET Core
To diagnose memory usage effectively, you need visibility into what the application is doing at runtime. Microsoft provides a suite of excellent cross-platform CLI tools designed specifically for this purpose.
dotnet-counters
This is a first-line diagnostic tool. It allows you to monitor performance counters in real-time without pausing your application. You can track CPU usage, GC heap size, allocation rate, and the number of GC collections per second.
dotnet-dump
When you need to know exactly what is in memory, dotnet-dump allows you to capture a full core dump of the application’s memory space. This dump can then be analyzed offline.
dotnet-gcdump
Unlike a full memory dump, dotnet-gcdump specifically triggers a Garbage Collection and captures the object graph (the GC roots). It is lightweight and perfect for identifying memory leaks in managed code without capturing the entire memory space of the server.
Visual Studio Diagnostic Tools
If you are developing locally, Visual Studio has a built-in memory profiler. You can take memory snapshots before and after a specific action (like rendering a complex web page) and compare them to see exactly which objects were added to the heap and never removed.

4. Step-by-Step Guide to Diagnosing a Memory Leak
If you suspect a memory leak in your production or staging environment, follow this systematic process.
Step 1: Establish a Baseline and Monitor
Start by observing the application using dotnet-counters. Run the following command against your ASP.NET Core process:
dotnet-counters monitor -p <process-id> --counters System.Runtime
Look closely at the GC Heap Size (MB) and the Gen 2 GC Count. If the heap size continuously grows and never drops back down after a load test, you have a verified memory leak.
Step 2: Capture a GC Dump
Once the memory has grown to an abnormal size, capture a snapshot of the heap using dotnet-gcdump:
dotnet-gcdump collect -p <process-id>
This generates a .gcdump file. Wait a few minutes, perform the actions that trigger the high memory, and take a second dump.
Step 3: Analyze the Snapshots
Open the .gcdump files in Visual Studio or JetBrains dotMemory. You want to use the Diff (Compare) feature to look at the delta between the two snapshots.
-
Sort the comparison by Size Diff or Count Diff.
-
Look for objects that multiplied rapidly between the two snapshots.
-
If you see millions of strings or custom objects, look at the GC Roots (the chain of references keeping the object alive). This will usually point you directly to the offending class, singleton, or event handler in your code.
Step 4: Investigate Server GC vs. Workstation GC
Sometimes, high memory usage is actually by design. ASP.NET Core defaults to Server GC mode. Server GC creates a separate managed heap and garbage collector thread for every logical CPU core on your server. This maximizes throughput for high-traffic web applications but can consume significantly more idle memory than a standard desktop application.
If you are running many small microservices in memory-constrained environments (like low-tier Docker containers), you might be better off switching to Workstation GC in your .csproj or appsettings.json to conserve RAM.
5. Best Practices to Prevent High Memory Usage
Once you have diagnosed and fixed the immediate issue, implement these best practices to ensure your ASP.NET Core application remains lean and performant.
-
Use
IHttpClientFactory: Never instantiateHttpClientmanually in a web app. RegisterIHttpClientFactoryin yourProgram.csand inject it to manage socket lifetimes efficiently. -
Implement Streaming: Instead of reading large files into memory (e.g.,
File.ReadAllBytes()), useFileStreamand process data in small chunks. This keeps data out of the Large Object Heap. -
Enforce Cache Limits: Always apply an absolute expiration or a
SizeLimittoMemoryCacheto ensure it eventually evicts old data and frees up RAM. -
Embrace
usingStatements: Ensure every class that implementsIDisposableis wrapped in ausingstatement or cleanly disposed of in afinallyblock. -
Use Structs for Small Data: For small, short-lived data structures, consider using
struct(value types) instead ofclass(reference types) to reduce GC pressure.
Conclusion
Diagnosing high memory usage in ASP.NET Core can feel intimidating, but by utilizing tools like dotnet-counters and dotnet-dump, you can isolate and eliminate bottlenecks with surgical precision. Clean coding practices, proper resource disposal, and understanding the .NET Garbage Collector are your best defenses against runaway RAM consumption.
For applications that demand maximum uptime and robust performance, having a highly optimized codebase is only half the battle; the other half is your hosting environment. Running your applications on reliable infrastructure, like the optimized Windows hosting environments provided by ASPHostPortal, ensures your modernized .NET applications have the dedicated resources and stability they need to scale seamlessly under heavy load.

Javier is Content Specialist and also .NET developer. He writes helpful guides and articles, assist with other marketing and .NET community work

