Using PerfView to Diagnose a .NET Memory Leak
I recently worked with a customer that was experiencing a memory leak with custom code running in Outlook. They were having trouble isolating the source of the leak, and they called us to help. There are several ways to dig into the process and profile the memory, but each have their own challenges and require some amount of supposition and guesswork.
When looking into these types of memory leaks, I have used a variety of tools over the course of my career, including DebugDiag, VMMap, and WinDBG with SOS. However, in investigating this particular leak, I came across a relatively new tool created by the .NET Performance Testing team called PerfView. This tool proved to be much easier to use in this situation, and it did not require multiple, cryptic steps, such as capturing multiple memory dumps and comparing the .NET object counts from one dump to the next. Instead, PerfView was able to capture multiple snapshots of the heap, compare those snapshots, and provide a listing of what was different between them.
To provide you with an idea of how simple this tool can be to help you find leaks, I created a simple application that contains a supposed “leak”. In reality, a ‘leak’ in a garbage collected runtime, like .NET, is typically just an object that is still being referenced and therefore cannot be removed from memory. It is not really a leak in the traditional sense, but it still is causing memory use to grow inside the process.
Knowing that my sample app has a leak, we can use PerfView to attempt to locate the source. The application has a simple WPF user interface which reports the size of the process. Over time, the process grows, but it gives no indication why. Below is a screen shot of the application:
To dig into this process, I used PerfView to inspect the heap. Below are the steps I took:
From the PerfView UI, choose “Take Heap Snapshot,” located on the Memory menu.
And choose the process you want to capture:
Click the “Dump GC Heap” button or simply double click on the process name.
When complete, PerfView will display the largest objects, sorted by the largest contributors.
As you can see, my sample application has an ArrayList as its largest contributor to the memory. That does not, however, necessarily mean that this object is the source of any leak. The largest object in an application may be a business object or some other component that exists to support the application’s functionality. In order to find the source of a leak, multiple snapshots must be captured and compared over time.
To capture another snapshot, simply return to PerfView’s main window and choose “Take Heap Snapshot” again from the Memory menu. Leave the current snapshot open so that you will be able to use it as a baseline when comparing it to the next snapshot. After capturing the second snapshot, you should have a second “Stacks” window open which looks similar to the first. To compare this snapshot with the first, locate and open the “Diff” menu. The first item in the list (assuming you did not close it) should be your original snapshot. (If the original snapshot was closed, you can reopen it from the main PerfView window.) Select the baseline snapshot and allow PerfView to compare the two.
After the Diff is created, you will see a screen that looks similar to the Stacks screens that displayed each snapshot.
In a Diff view, the columns to the right of the object types indicate the percentage and raw value differences between the two snapshots. In the case above, notice that the “Totals Metric” value in the header section of the window, near the upper left corner, shows that the total size difference between the two snapshots is about 3.4 MB. In the main section of that same window, we can see that there is/are ArrayList object(s) that have contributed about 99.8% to that difference, or about 3.4MB. If we double-click on the ArrayList line, we can see what objects use that particular type and how much each of those referring objects are contributing to the increase.
From this screen shot, we can see that an object called MyLittleLeaker.Leaker.a makes up the largest difference in memory between the two snapshots. This object is indeed the source of my leak.
As you can see from the contrived example above, PerfView can help provide insight into what is changing over time inside your application, and it can be much less cumbersome than capturing and interpreting memory dumps with commands that are hard to remember.
The Microsoft .NET Performance team has created a series of videos, which are posted on Channel 9, on how to use PerfView in several scenarios, including live profiling and investigating high CPU scenarios. Take a look at the Channel9 PerfView Tutorial to learn more.
- Download PerfView
- Publication of the PerfView performance analysis tool!
- Next Version of PerfView has been released!
- Channel9 PerfView Tutorial