Av rating:
Total votes: 87
Total comments: 10


Mike Bloise
Tracing memory leaks in .NET applications with ANTS Profiler
29 September 2006

Mike Bloise is a lead developer at Recognin Technologies. This case study recounts his experiences on a recent CRM project, built using C#, where he found himself facing severe memory leak issues and a very tight deadline.

Memory leaks are relatively easy to introduce into your programs when coding in C or C++ (no-one could have enjoyed having to write destructors for every single one of their C++ classes). However, when you code in a .NET language, such as C#, you are working in managed code, with automatic garbage collection, so memory management becomes a bit of a non-issue, right?

That certainly pretty much describes my mindset when I developed the brand new C# 2005 desktop version of my company's sales and CRM application. I was new to C#, and while I was aware that there could still be problems if references weren't cleaned up properly, I hadn't given memory management much thought during development. I certainly wasn't expecting it to be a major issue. As it turns out, I was wrong.

The problems begin…

I knew I was in trouble the first time my customer called with specific memory use numbers from the Windows Task Manager. Jack is a salesperson by trade, but by volunteering to beta-test my new desktop application, he had unknowingly put himself in line for a crash course in memory leak awareness. "The memory is up to five hundred thousand K since restarting this morning", he said, "What should I do?"

It was a Friday afternoon and I was at an out of town wedding for the weekend. Jack had noticed this issue the day before and I had advised the temporary fix of close-out-and-come-back-in. Like all good beta testers he was happy to accept the temporary solution, but like all excellent beta testers he wasn't going to accept having to do a temporary fix over and over.

Jack wasn't even the heaviest user of the application and I knew that the installed memory of his machine was above average, so going live wouldn't be possible until I could trace and fix this leak. The problem was growing by the minute: the scheduled go-live date was Monday and I'd been on the road, so I hadn't been able to look through the code since the memory issue had arisen.

Fighting memory leaks armed only with Task Manager and Google

I got back home on Sunday evening and scoured the search engines, trying to learn the basics of C# memory management. My company's application was massive, though, and all I had was the Task Manager to tell me how much memory it was using at any given time.

Displaying an invoice seemed to be part of the problem; this was a process that involved a large number of different elements: one tab page, a usercontrol on the page, and about one hundred other controls within that usercontrol, including a complicated grid control derived from the .Net ListView that appeared on just about every screen in the application. Every time an invoice was displayed, the memory use would jump, but closing the tab wouldn't release the memory. I set up a test process to show and close 100 invoices in series and measure the average memory change. Oh no. It was losing at least 300k on each one.

By this point it was about 8pm on Sunday evening and needless to say, I was beginning to sweat. We HAD to go live the next day. We were already at the tail end of our original time estimate, other projects were building up, and the customer was already starting to question the wisdom of the entire re-design process. I was learning a lot about C#'s memory management, but nothing I did seemed to keep my application from continuing to balloon out of control.

Enter ANTS

At this point, I noticed a banner ad for ANTS Profiler, a memory profiler for .NET. I downloaded and installed the free trial, mentally composing the apologetic 'please give me a few more days' email I would need to write the next morning if I didn't find a resolution.

How ANTS worked was pretty clear as soon as I opened it. All it needed was the path to the . exe, after which it launched the system with memory monitoring turned on. I ran through the login process in my application, and then used the main feature in ANTS to take a 'snapshot' of the application's memory profile before any invoices or other screens had been shown.

Browsing that first profile snapshot, I was stunned at the amount of information available. I had been trying to pinpoint the problem using a single memory use number from the Task Manager, whereas now I had an instance-by-instance list of every live object my program was using. ANTS allowed me to sort the items by namespace (the .NET ones as well as my own), by class, by total memory use, by instance count, and anything else I could possibly want to know.

Armed with this information, I mentally put my apology email on hold, brought up my application, ran the process that displayed 100 invoices, and then took another snapshot. Back in ANTS, I checked the list for instances of the main invoice display usercontrol. There they were; 100 instances of the control along with 100 instances of the tab and 100 instances of everything else, even though the tabs themselves had all been closed on the screen.

The obvious problem: objects not being removed from an array

In my research I had learned that the .NET memory management model uses an instance's reference tree to determine whether or not to remove it. With a bit more clicking in ANTS, I found that it could show me all of the references both to and from every instance in my program.

Using ANTS to navigate forward and backward through the maze of linked references, I was quickly able to find a static ArrayList to which all displayed tabs were added, but from which they were never removed.

After adding a few lines of code to remove each tab from this collection as it was closed, I re-ran the profiler and the 100 invoice process and voilà; the tabs, the main usercontrol, and nearly all of the sub-controls were gone. It got even better too: the memory increase after each invoice was down to a fifth of what it had been, which changed the memory leak from a major concern down to a minor annoyance. The next day we went live, and although issues of all sizes arose, none of them was caused by the leak.

The more subtle problem: events listeners and the ListView object

Later that week, however, Jack's calls resumed: "The memory is still slowly creeping up; what's going on?" I didn't know, but at least I knew where to look now. I used ANTS to see if I could locate the remaining leak. What I found was that one of the sub-controls of the main invoice usercontrol, the ListView-based one that formed a primary part of the interface, was being held in the reference tree by what appeared to be standard event handlers like OnClick and MouseMove, hooks that had been added using the Visual Studio IDE and that would have been, I thought, cleared automatically.

This was really puzzling to me, and I wrote to Red Gate Software, the developers of the ANTS system, asking for some additional help. Their support staff promptly responded and explained that in situations with lots of complex references and event handlers, the .NET runtime can leave event handlers in place when they should be disposed. They suggested manually removing each handler in the Dispose method of the usercontrol that was causing the problem.

I added the 20 or so minus-equals statements to remove each handler, and for good measure, I added a System.GC.Collect()statement after the tab closing process.

Re-running the ANTS profiler and the 100 invoice process, I found that the memory use remained rock solid. Then, when re-checking the ANTS snapshot, I could see that all of the invoice-related controls had been released, and the memory use numbers in the task manager never moved.

I re-compiled and uploaded the new version. Now it was my turn to call Jack.

Summary

What did I learn from all this? Firstly, that the "it's managed code so we don't have to worry about memory leaks" assumption falls short of the mark.

Although automatic memory management in .NET makes our lives as .NET developers a whole lot easier, it is still easy to introduce memory leaks into your application. Even in managed memory, there can be issues. The memory manager cannot free memory that is still 'live' – and it will still be considered live if it is referenced directly or indirectly through the "spider's web" of references that link the various objects. Also, when complex reference trees and event handlers are involved, the memory manager doesn't always deregister these event handlers, and so the memory will never be released unless you forcibly release it.

Secondly, I learned that tracking down these sorts of issues equipped only with Task Manager was simply not possible – certainly not in the timeframe I had. Tools such as Task Manager (and Performance Monitor) were able to tell me that my application was using up a lot of memory, but I needed a dedicated memory profiler like ANTS Profiler to really show me what objects made up that memory and why they were still there.

You can download a free trial of ANTS Memory Profiler from Red Gate’s website and try it on your own application.



This article has been viewed 44734 times.
Mike Bloise

Author profile: Mike Bloise

Mike Bloise is the lead developer at Recognin Technologies in Raleigh, North Carolina. His focus in on building flexible, extensible application frameworks, and he currently uses C# 2005 for nearly everything. His daily bread is Rz3, an enterprise management system for electronic component distributors. When he's not programming, he enjoys thinking, talking, and writing about it.

Search for other articles by Mike Bloise

Rate this article:   Avg rating: from a total of 87 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: Coincidence
Posted by: Anonymous (not signed in)
Posted on: Tuesday, October 03, 2006 at 10:25 AM
Message: With mostly .NET knowledge from books rather than code (sadly I don't have time to port my VB6 app just yet!) I too thought that garbage collection was something I didn't need to do anymore.

Imagine my surprise (well ok, not really), when my first (very simple) web service ends up using more than 1gb memory in a relatively short space of time! Using ANTS Profiler, I was able to prove to the vendor of the 3rd party library I was calling, that it was they who were creating the memory leak(s).

Good job this article appeared now otherwise I wouldn't have considered trying the application - excellent timing!

Subject: time for a new name?
Posted by: nuri (view profile)
Posted on: Thursday, October 05, 2006 at 10:11 PM
Message: Interesting read. And yes, the .NET framework will not release your objects if it thinks they are still in use. My question though is "Is this a memory leak?". Could we come up with a new name for these such as "nay release" or something?

In C / C++ memory leaks usually referred to allocation of memory that never got released AND that the pointer to that memory was "lost". That is, not only did some memory get allocated and "forgotten", but the reference to it is no longer available and therefore it would never get released in the app lifetime. In your managed code, this is not the exact case. A programmer is unaware that object refs get created or tied to some object is NOT losing the reference to the allocated object. In fact, if the reference to all unused objects got lost then it would be a classic leak.

So yes, it's a problem - watch for these pesky delegates - but maybe it's time to coin a new term for it?

In any event, I'm bookmarking this article so that when I forget to release references, I know what to look for..

Subject: A couple of suggestions
Posted by: Anonymous (not signed in)
Posted on: Friday, October 06, 2006 at 4:50 AM
Message: ANTS Profiler is really good product.
A couple of suggestions to your code:
1. It is not a really good idea to call GC.Collect() just for a case
See http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx

2. It is a good practice to clean up IDisposable objects explicitly (calling Dispose() method), and not just let them be finalized by the CLR. So, if your controls are IDisposable and you do not dispose them, it would lessen the memory pressure, if you do that.

Subject: Excellent Post
Posted by: Anonymous (not signed in)
Posted on: Tuesday, October 17, 2006 at 11:22 AM
Message: We have a complicated application - ladden with "situations with lots of complex references and event handlers".

We have a usercontrol that absolutely will not release. I'm very enthusiastic to use the ANTS profiler and very hopeful that it will assist our efforts. Even if it doesn't, I'm happy to see corroboration of our experience from others as advanced with .NET as the author.

Subject: eye opener
Posted by: Anonymous (not signed in)
Posted on: Saturday, November 04, 2006 at 2:21 AM
Message: Garbage collection concept in .net had really made me think that i need not think and ook into releasing the memory references made explicitly. This article really has made me keep some time allotted separately to work on these things as in my earlier vb6 days. So too much of memory usage cases by me can be handled early and taken care of early. Great

Subject: Another approach exists
Posted by: Anonymous (not signed in)
Posted on: Saturday, January 20, 2007 at 6:50 AM
Message: The really _best_ tools to tracing memory leaks in .NET applications is YourKit .NET Profiler http://www.yourkit.com

YourKit automatically finds the biggest objects in memory (not with biggest shallow size, but with biggest retained size) and shown paths to these objects from GC roots. Yourkit can compare two memory snapshots and show new objesct, etc, etc.

Subject: question on removing the tabs
Posted by: Anonymous (not signed in)
Posted on: Wednesday, April 11, 2007 at 9:40 PM
Message: I am having a similar problem in my code where I am adding tab pages and forms and then closing them and I see the memory increasing everytime.
You mentioned that there is a static ArrayList to which the tabs get added and you wrote some code to remove it from the list.

Can you please tell how I can do the same thing?

Subject: Worth reading this article
Posted by: Ali W.Qureshi (not signed in)
Posted on: Monday, October 01, 2007 at 12:23 AM
Message: Hello,
It was good exp. to go through this article as i am into the same shoes of author, right now i am going to download the ANTS Profiler, then i will write more to this.


Subject: It's not the garbage collector's problem, it's the coders
Posted by: Anonymous (not signed in)
Posted on: Wednesday, January 09, 2008 at 11:12 AM
Message: Make sure when you register for events, you unregister.

When writing C# code, you DON'T have to care about freeing up memory - just make sure you clean up your references appropriately.

Can you say "unit testing"?

I knew you could.

Subject: CLR Profiler
Posted by: David (view profile)
Posted on: Thursday, January 24, 2008 at 4:42 PM
Message: In your case, the CLR Profiler, free from Microsoft, could have pointed you to your problem just as easily.

 






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.




Has .NET Reflector Saved Your Bacon?
 We think Reflector is a fantastic tool, and we know you do too. We'd love to hear about the times... Read more...

The Managed Heap
 Because Red-Gate's .NET team works closely with the users of their products in order to try to fit the... Read more...

Using Three Flavors of LINQ To Populate a TreeView
 LINQ is a valuable technology. LINQ to XML, LINQ to Objects and LINQ to XSD, in particular, can save... Read more...

How to build a Query Template Explorer
 Having introduced his cross-platform Query Template solution, Michael now gives us the technical... Read more...

How to Create Event Receivers for Windows SharePoint Services 3.0
 You'll be surprised how often that you'll use event receivers instead of Workflow in order to implement... 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 150,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk