Click here to Skip to main content
Click here to Skip to main content

Tracing memory leaks in .NET applications with ANTS Profiler

By , 12 Feb 2007
 

Editorial Note

This article is in the Product Showcase section for our sponsors at CodeProject. These reviews are intended to provide you with information on products and services that we consider useful and of value to developers.

This is a showcase review for our sponsors at CodeProject. These reviews are intended to provide you with information on products and services that we consider useful and of value to developers.

Introduction

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 subtler 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 Profiler 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 Profiler 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Red Gate Software
Red Gate Software Ltd.
United Kingdom United Kingdom
Member
Organisation
4 members

Red Gate Software produces ingeniously simple tools for Microsoft technology professionals worldwide. We currently specialize in MS SQL Server, Cloud, .NET and Oracle database tools. Established in 1999, we are based in the beautiful, if rather flat, university town of Cambridge, UK

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionYourKit is also very good .NET profilermemberVladimir Kondratyev4 May '12 - 3:42 
YourKit .NET profiler is also a very good profiler for managed code. It has free 15 days trial license for fully featured version http://www.yourkit.com/dotnet/
Questiontry deleaker.memberMast Avalons4 Jan '12 - 7:50 
Try deleaker.... it's tool for debuuging memory leaks.....
AnswerRe: try deleaker.memberJohnDepth20 Feb '12 - 6:24 
Cool tool Smile | :) I also use it but for debugging GDI leaks. Poke tongue | ;-P
AnswerRe: try deleaker.memberJohnDepth17 May '12 - 18:44 
Interesting tool.... Similar to MemCheck...
GeneralRe: try deleaker.memberMast Avalons22 May '12 - 2:04 
You are wrong. It's not like MemCheck.
GeneralRe: try deleaker.memberJohnDepth23 May '12 - 7:08 
why do you think so?
GeneralRe: try deleaker.memberMast Avalons24 May '12 - 8:56 
MemCheck cannot search GDI leaks. Dead | X|
GeneralRe: try deleaker.memberJohnDepth25 May '12 - 10:07 
Don't dispute with me. I'm clever. Big Grin | :-D
GeneralRe: try deleaker.memberMast Avalons28 May '12 - 10:48 
I see Smile | :)
Generalwedding or not...membermkoenings16 May '07 - 3:06 
this article saved my life. thanks Big Grin | :-D . The unregistering of all events in the generated code in the dispose helped to release a complex usercontrol from memory.
cheers, mik

GeneralANTS UsagememberCharuT27 Feb '07 - 18:41 
I guess the article is about ANTS usage and simply demonstrates a scenario to use it. Again, its a sponsored ad-types. Definitely, comparing a Profiler to a Task Manager is NOT a good idea as the purpose of both is totatlly different.
 
ANyway, a good start for ANTS Profiler advertisement. Smile | :)
 
Charu.
 
Charu.

GeneralNo memory leaks heremembermammorysam12 Feb '07 - 22:28 
Sorry to be pedantic, but this is about memory management not leaks.
 
Unfotunately .Net does indeed still have some real memory leaks, and I thought this article might help me - it didn't.
 
I looked at your profile - given the number of articles you have written about ANTS, i find it hard to believe in this article when you say you just stumbled upon it for this 'leak'. Why not just be honest and admit you are affilliated with them?
GeneralRe: No memory leaks heremvpStephen Hewitt13 Feb '07 - 2:38 
mammorysam wrote:
Sorry to be pedantic, but this is about memory management not leaks.

  Sorry to be pedantic myself but a memory leak is mearly one form of memory miss-management: this article is indeed about memory leaks.

 
Steve

GeneralMemory build-up is a leakmemberZijian21 Feb '07 - 13:42 
This article is from a sponser called Rea Gate Software. If you ever eyed on the first paragraph of the article.
=============================================
This is a showcase review for our sponsors at CodeProject. These reviews are intended to provide you with information on products and services that we consider useful and of value to developers.
========================================
 
Regarding to memory leak, for traditional point of view, there's no leak in .NET, since object without reference will be collected, and objects with references will stay there, while in "unmanaged" world, objects without reference can still be there, definitely memory leaks.
 
However, from certain point of view, unexpected memory build-up is memory leak, due to unexpected growth of the reference trees. Yes, it is not leak of memory management of GAC, but it is leak of memory management of you. Memory usage is out of your control.
 
While task manager or Processor Explorer could provide a global view of memory usage, a memory profiler can provide detailed view. Of course in .net world it is almost impossible for a profiler to tell memory leak, as profilers like FastMM in unmanaged world can, because it is hard to condition a profiler to tell which referenced objects are not expected, only the programmers can tell.
 
Zijian

GeneralRe: Memory build-up is a leakmemberUncle Serge6 May '07 - 23:02 
Ok, it's a sponsored article. Then I don't understand what is all that crap for? I mean, the wedding and other bullsh*t.
GeneralRe: Memory build-up is a leakmemberbalazs_hideghety29 May '07 - 8:56 
Hmm, an interresting opinion. If it'd be so easy...
 
I have a running project. As you said, there's no real memory leak, because all objects ae garbage collected. Still after a time the applications gets a 100MB big in memory.
 
Firt i thought that my special classes (with byte[] arrays) are not freed up. So i implemented IDisposable interface for them, and always set all the byte[] values to null when disposing. Even so i left the work on the GC (.Dispose was not called from the code, only the GC initialized the ~destructor call).
 
Anyhow, my code worked - i've did a check with ANTS profiler.
 
What i've found that the most part of the memory was occupied by Byte[] and String object. But these objects are not created from my class, otherwise the column "Source file" on the panel "All objects panel" would not be empty.
 
So do not have any clue, what to do... These objects get's larger and larger - even if my classes with Byte[] objects are not used. The whole app is a win GUI.
 
Anyone could help?!
Thanx.
 
C#, ASPX, SQL, novice to NHibernate

AnswerRe: Memory build-up is a leakmemberRobert Chipperfield7 Jun '07 - 2:59 
Hi there,
 
I was one of the developers working on ANTS Profiler 3 - maybe I can help.
 
If you select one of the objects you're interested in, then look at the "call stack" view, you should see the call stack at the point where that object was allocated. This should give you a much better idea of where the objects are coming from, even if they aren't directly allocated by your code.
 
You could also have a look at the "hierarchy" view, which shows you which objects are currently referencing the object you're looking at. By clicking on the parents, you can navigate back through this hierarchy and determine why they aren't being released.
 
I hope this helps,
Robert
 
Robert Chipperfield
Software Engineer, Red Gate Software

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 12 Feb 2007
Article Copyright 2007 by Red Gate Software
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid