Memory Management in .NET is a broad topic with a lot of “mysteries” that come with it. Heavy memory usage as well as frequent allocations affect the performance of your application because lots of time is spent by the Garbage Collector to free up space in the individual Heap Generations. It’s a good practice to monitor your applications memory behavior. Several blog entries exist that describe which performance counters to analyze.
Monitoring Garbage Collector Activity
There are 4 different performance counters per .NET Process that tell us something about the GC Activity:
- # Gen 0 Collections
- # Gen 1 Collections
- # Gen 2 Collections
- % Time in GC
The first 3 measures give us the total count of collections that happened in the individual generation since the process started. % Time in GC tells the time that was spent by the GC since the end of the last GC run. Based on best practices this number is good under 10% with occasional spikes above that. If you however see more than 50% of time spent in GC you should drill into that problem.
Mystery with large 2nd Gen Heap
I created a sample ASP.NET application with a built-in memory leak. I ran it on the .NET 3.5 SP1 Framework and hosted the application in the Visual Studio WebDev.WebServer. I wanted to test out if the .NET Performance counters actually provide meaningful data. My memory leak creates several thousand objects in a background thread on a one second interval – putting them all in a static ArrayList so that they won’t be collected.
The following illustration shows the memory graph of my app.
The graph shows the constantly growing 2nd Generation Heap – caused by my memory leak. As you can see from the graph above – I stopped my memory leak from creating new objects at about 12:50. Until that time I also saw the number of GC Collections constantly rising. These counters also came to an hold at 12:50 as the following graph shows:
I also expected the % Time in GC to come down when stopping producing new objects. It however turned out that – despite no new objects were allocated – this counter remained on a very high level:
It’s a mystery to me why the % in GC remained above 60% in my test scenario. It might be due to the fact that I crossed a certain memory usage threshold which makes the GC run more often. It might also be due to the fact that I created more than 1 million objects and that this is a threshold the GC has problems with. Another explanation is that the counter might got screwed up and is no longer delivering accurate values.
Do not only check your heap counters. Also, watch the activity of your Garbage Collector which gives you an indication about how your application is allocating memory. Follow the best practices in terms of the thresholds of these counters and start analyzing the internals of your applications memory management in case you experience unusual behavior.
Update: If you’re interested in learning more about .NET performance tools, you might also be interested in my latest White Papers about Continuous Application Performance for Enterprise .NET Systems.