Tracking Down .NET Memory Leaks with Visual Studio Diagnostic Tools
Stop guessing why your .NET app is crashing with OutOfMemoryExceptions. Learn how to use Visual Studio's Diagnostic Tools to capture memory snapshots and identify leaking objects.
11 Sept 2026, 18:10 UTC

The "Slow Crawl" Problem
You've deployed a .NET service that works perfectly during a 10-minute smoke test, but after three days in production, the server starts swapping to disk and eventually crashes with an OutOfMemoryException. This is the classic memory leak: a slow accumulation of objects that the Garbage Collector (GC) cannot reclaim because they are still being referenced by some long-lived root.
The goal isn't just to see that memory is increasing, but to identify exactly which objects are staying alive and what is holding onto them. Visual Studio's built-in Diagnostic Tools window allows you to do this without installing external profilers.
Analyzing the Memory Trend
When you start debugging (F5), the Diagnostic Tools window typically appears on the right. The Memory graph provides a real-time view of the managed heap. A healthy application usually shows a "sawtooth" pattern: memory climbs as objects are allocated and drops sharply when the GC runs.
If the baseline of those sawtooth drops keeps rising over time, you have a leak. To diagnose this, you need to move from observing the graph to capturing Memory Snapshots. A snapshot is a point-in-time recording of every object currently residing on the managed heap.
Using Snapshots to Find the Leak
The most effective way to find a leak is the "diff" method. By comparing two snapshots, you can filter out the noise of permanent application state and focus only on what grew between two points in time.
Example Workflow: Identifying a Leaky Event Handler
Imagine a scenario where a UserSession object is created for every login but is never disposed of because it's subscribed to a static GlobalSettings.Changed event.
- Baseline: Start the app and reach a steady state. Click Take Snapshot in the Diagnostic Tools window. This is Snapshot 1.
- Trigger: Perform the suspected leaking action (e.g., log in and log out a user) 10 times.
- Comparison: Click Take Snapshot again. This is Snapshot 2.
- Analysis: Click the number in the Objects (Diff) column of Snapshot 2.
Visual Studio will open the Heap Profiler view. Look for the object type with the highest positive diff. In this case, you would see UserSession with a count of +10. By selecting that object, you can view the Paths to Root, which will reveal that the GlobalSettings static event is holding the reference, preventing the GC from collecting the session objects.
CPU Hot Paths and GC Pressure
Memory leaks aren't always about forgotten references; sometimes they are about allocation churn. If the CPU graph shows constant spikes and the Events tab shows frequent GC Garbage Collection entries, your app may be allocating short-lived objects too quickly.
Use the CPU Usage tool during a recording session to find "hot paths." This identifies methods consuming the most processor cycles. If a method is spending significant time in System.Private.CoreLib allocation routines, you may need to implement an ArrayPool or switch to ValueTask to reduce the pressure on the heap.
Trade-offs and Technical Limitations
While integrated, these tools have specific constraints:
- Performance Overhead: Taking a snapshot pauses the application execution (STW - Stop The World). In a high-throughput environment, this can cause timeouts in connected services.
- Debugger Memory: The Visual Studio process itself must hold the snapshot data. If you capture a 2GB heap, Visual Studio's own memory usage will spike significantly.
- Debug vs. Release: Always verify leaks in a Release configuration if possible. Debug builds disable many compiler optimizations and can produce different object lifetimes, potentially hiding or exaggerating certain behaviors.
Verifying the Fix
Once you have removed the offending reference (e.g., by unsubscribing from the event in IDisposable.Dispose()), verify the result using the same snapshot method. The Objects (Diff) column for the leaked class should now show 0 or a negligible number after the operation is completed and a manual GC is triggered.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.