Click here to monitor SSC
Av rating:
Total votes: 13
Total comments: 2


Laila Lotfi
Profiling the Memory Usage of a .NET Application with ANTS Memory Profiler 5
04 June 2009

 We were recently taken to task by a reader who felt that the one place he'd expect to find a nice simple introduction to memory profiling with ANTS Memory Profiler™ 5 would be on Simple Talk.  Memory profiling has an intimidating reputation, but fortunately Laila Lotfi came up with a simple introduction that even ordinary mortals could appreciate.

Introduction

Automatic memory management in .NET makes development a lot easier; however, it's still easy to introduce memory leaks into your application. For example, in a complex application, it's easy to forget to unregister event handlers, and these are notorious for holding on to objects which you don't need to keep in memory any more. This typically leads to an increase in memory usage which, if it remains unchecked and unresolved, can lead to your application exhibiting poor performance, or even running out of memory and crashing. This is where a memory profiler becomes necessary.

The basics

ANTS Memory Profiler is a memory profiler for .NET applications – including ASP.NET web applications – that will help you locate memory leaks, investigate your application's memory usage and perform health checks on your code. In essence, ANTS Memory Profiler will help you identify how to reduce your application's memory footprint.

Let's illustrate this point by using ANTS Memory Profiler 5 to locate a memory leak in a desktop application called QueryBee. It is a simple WinForms application for running queries against SQL Server databases. It is made up of a database connection dialog…

image

Fig. 1: QueryBee – database connection dialog.

…and a query window to query the database.

image

Fig. 2: QueryBee – the query window.

We know our QueryBee application is leaking memory because, every time we open a query window and close it again, our memory usage keeps on increasing.

This is the profiling strategy we're going to use:

  1. Wait for QueryBee to open.
  2. Take a first snapshot without using the application – this first snapshot will be used as a baseline.
  3. Interact with QueryBee – connect to database, enter a SQL query in the query form, and execute the query and close the form.
  4. Take a second snapshot.
  5. Examine the comparison the profiler shows us after it has finished taking and analyzing the second snapshot.

Let's get started.

On opening up ANTS Memory Profiler, we are presented with a setup dialog (Fig. 3).

image

Fig. 3: The ANTS Memory Profiler 5 setup dialog.

All we need to do is point it at QueryBee and click Start Profiling. The profiler starts up QueryBee and begins collecting performance counter data (Fig. 4).

image

Fig. 4: Whilst profiling, ANTS Memory Profiler collects performance counter data. The profiler is now profiling our application.

At this point, we take a baseline snapshot by clicking on the Take Memory Snapshot button in the top-right corner. The profiler forces a full garbage collection and takes a snapshot of the heap memory it is using, and we get a first set of results (Fig. 5)

image

Fig. 5: Results from our first snapshot – summary screen.

Now, we go back to QueryBee and perform the tasks which we think cause the memory leak: we get QueryBee from the system tray, we select a database, enter a SQL query in the query window, execute the query, and obtain some results.
 

image

Fig. 6: QueryBee – the results are displayed in a grid.

Now that we've got the results, we close the form.

At this point, the window is gone, so we expect the memory usage to fall back to where it was around the first snapshot, but that is not the case.

image

Fig. 7: Despite closing our query window, the memory usage is still rising.

So What's Happening Here?

So what's happening here? We take a second snapshot and get the results (see Fig. 8).

(We're not planning on taking any further snapshot, so we click on the Stop Profiling button.)

image

Fig. 8: The summary pane compares the results of the two snapshots.

We switch to the Class List to find out more. The Class List gives us a fuller picture of what's in the snapshot.

image

Fig. 9: The Class List allows you to compare memory usage in both snapshots in more detail.

We sort by Size Diff to see what has increased the most in size since our baseline.

We have 816 classes in our class list, so let's use the Filter panel on our left to reduce the number of classes in the list. Because we have taken two snapshots, one very useful filter is the Comparing Snapshot filter. We only want to see new objects that have been created since the baseline, so we select the Only new objects filter.

image

Fig. 10: We've now applied one filter to see only new objects that have been created since our baseline snapshot.

We can see the impact of this filter on the bar above the class list – we're left with 639 classes. We have already removed about a quarter of the classes. That's a pretty good start.

Let's reduce this further by applying another filter.

We know that we call Dispose() a number of times in our application. Memory leaks often occur when an object cannot be garbage collected, even if Dispose() has been called, because it is still being referenced by another object. So, let's apply the filter Disposed objects which are still in memory.

image

Fig. 11: We now apply a second filter, and that has dramatically reduced our number of classes.

That's had a huge impact on the number of classes left. Scanning down the list, we can also see that there is an instance of our QueryForm class in the list.

That's not right. We closed that form after the query had completed. To find out why it is still being held in memory, we'll look at this instance of QueryForm, by clicking on the blue icon next to it.

image

Fig. 12: We access the Instance list by clicking on the blue icon next to QueryForm.

image

 Fig. 13: On its own, QueryForm is not that big, but the Size with Children column is showing that the one instance of QueryForm is holding on to over 80 MB.

 We find that QueryForm is not that big, but the Size with Children column is showing that the one instance of QueryForm is holding on to a reasonable chunk of memory.

So, we know we're leaking QueryForms, but we need to find out why. Let's create an Object Retention Graph by clicking on the  icon.

image

Fig. 14: This Object Retention Graph shows us what is still referencing our QueryForm.

This Object Retention Graph shows what is still referencing our QueryForm. Once we figure this out, we'll be able to go back into our code to break the chain of references that is keeping the QueryForm in memory.

There's a handy hint in red telling us to start at the bottom and work our way up the graph until we find a reference that needs to be broken. We'll just need to break the chain at one point to allow the garbage collector to clean up everything below that.

First, the graph is telling us that this System.EventHandler is referencing QueryForm and, if we step up one more level, it's telling us that the event handler is referenced by our ConnectForm instance – this is the form that asked us for the database connection details. In other words, the ConnectForm is holding on to the QueryForm via an Event Handler.

If we look at this node more closely, we see that it's actually being referenced by the ConnectForm's Foregrounded field.

 Let's find this Foregrounded event in our code. We right-click on the QueryBee.ConnectForm node and open the ConnectForm source code in Visual Studio™.

image

 Fig. 15: Foregrounded event in the ConnectForm source code.

The profiler automatically jumps to the Foregrounded event. We check where it is being used, by right-clicking on Find All References.

image

Fig. 16: The Foregrounded event is used in three places.

 We've got three usages and we find that the last usage is where QueryForm registers for the Foregrounded event, but it doesn't look like it unregisters. If we fix that, then the memory leak should go away.

The place to unregister that event is in the QueryForm's Dispose() method.

image

image

Fig. 17: QueryForm.cs file.

But since QueryForm doesn't have a reference to the ConnectForm, we are going to have to store that in a member field.

image

image

Fig. 18: QueryForm.cs file.

Now we can modify Dispose() in the QueryForm.Designer.cs file.

image

Fig. 19: QueryForm.Designer.cs file: Dispose() before modification.

image

Fig. 20: QueryForm.Designer.cs file: Dispose() after modification.

We're done, so we rebuild our application in Visual Studio.

So. Have we Really Fixed it?

Back in the profiler, we start up a new profiling session. We want to find out that the reference to the QueryForm has disappeared.

Notice that it remembered our settings from last time, so all we need to do is click Start Profiling.

image

Fig. 21: The settings dialog remembers settings from last time.

QueryBee opens up and we take a first snapshot to use as a baseline.

image

Fig. 22: Results from first snapshot.

We connect to a database and execute a SQL query.

Now, we'll take an extra snapshot, because we want to be able to verify that the query form has disappeared. This will give us results for snapshot 2.

Finally, we close the query window with the results grid and we take a third snapshot.

image

Fig. 23: Summary screen comparing snapshots 2 and 3.

We switch to a comparison between snapshots 1 and 3, using the snapshot selection fields just under the timeline.

image

Fig. 24: Summary screen comparing snapshots 1 and 3.

 Let's see if there's a QueryForm still in the class list.

image

Fig. 25: The timeline shows a sudden drop in memory.

No, it's gone. We're no longer leaking the form.

As you saw, it was fairly easy to track down a form which was being leaked.

If you would like to try this on your own application, you can download a free 14-day trial from Red Gate's website

kick it on DotNetKicks.com


This article has been viewed 9613 times.
Laila Lotfi

Author profile: Laila Lotfi

Laila is a Simple-Talk editor who also works as a Brand Manager in the .NET tools division of Red Gate Software. She writes the .NET Reflector newsletter, so if you have any feedback on the content that you want covered, please get in touch with her.

Search for other articles by Laila Lotfi

Rate this article:   Avg rating: from a total of 13 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Great Article but...
Posted by: TechMate (view profile)
Posted on: Wednesday, July 22, 2009 at 7:55 AM
Message: This is a great post...
I see quite a few articles on profiling Windows App but not a sample walkthrough example for a WEB APP? Is it that difficult to profile memory on a web app? There are so many classes (built-in) and identifying memory leaks is lot more difficult and challenging... I am still a beginner to Antz Profiler but I dont seem to find enough advanced examples or walkthroughs or case studies to really help profile a web app. (maybe I am not looking in the right place..?)

Subject: Request for the QueryBee source
Posted by: dkalemis (view profile)
Posted on: Thursday, July 23, 2009 at 2:08 PM
Message: Could you please post the source code
for the whole QueryBee application online?

 






recommended site pinvoke

PInvoke.net is a user-driven wiki which provides .NET developers with native method signatures, so they don't have to spend time writing them from scratch.




TortoiseSVN and Subversion Cookbook Part 3: In, Out, and Around
 Subversion doesn't have to be difficult, especially if you have Michael Sorens's guide at hand. After... Read more...

Feature Usage Reporting in Early Access Programs
 After doing Web development, you can get very used to the luxury of having basic information about your... Read more...

Feature Usage Reporting in Early Access Programs
 After doing Web development, you can get very used to the luxury of having basic information about your... Read more...

TLS/SSL and .NET Framework 4.0
 The Secure Socket Layer is now essential for the secure exchange of digital data, and is most generally... Read more...

SmartAssembly: Eating Our Own Dogfood
 Quite often at Red Gate, we are some of our own most enthusiastic software-users. SmartAssembly is a... Read more...

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Configuring Forms Authentication in SharePoint 2007
 Damon Armstrong provides a step-by-step guide to the processes, quirks and pitfalls of setting up... Read more...

Over 400,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk