Three techniques for tracking down memory leaks due to undisposed objects
People often ask me for tips/tricks on how to find out which objects are not being properly disposed. But before I go any further I should talk briefly about why that's a problem.
If an object is Finalizable that means that the runtime must run the Finalize method on that object (in C# you use the ~Class syntax to define the finalizer) before the memory can be reclaimed. The mechanism for doing this is fairly straightforward -- whenever an object that is finalizable first becomes unreachable it is put on a queue of objects needing finalization. Naturally we only discover that the object is unreachable during a garbage collection, and since we need to keep the object alive to finalize it the object must survive that collection. When the finalizer runs the object will be again unreachable and on the next collection the memory will be recovered as usual. See Garbage Collector Basics and Performance Hints and Two Things to Avoid for Better Memory Usage for more details.
Now the problem with all this is that it means every Finalizable object must survive at least one collection and so they are more likely to be promoted into older generations where getting rid of them is more expensive. Furthermore, collections of those older generations is less frequent so the memory for the finalizable object may not be reclaimed as quickly as we might like even if the finalizer is run comparatively soon.
To avoid these problems, every finalizable object should also be IDisposable (and most are). If an object is IDisposable it means that when you are done with it you can call the Dispose() method which releases any unmanaged resources associated with the object and tells the runtime that the object no longer needs to be finalized. As a consequence the resources are returned to the system as quickly as possible and the object doesn't need to survive for finalization -- it dies nice and quick like a regular object.
With that background I can get back on topic: Sometimes despite our best efforts we forget to call the Dispose method on every Disposable object. When that happens those objects go through finalization. If that happens a lot we can get Mid-Life Crisis.
So, how do we detect that this is happening? And if it is happening how do we track down the source of the leak? I have some approaches for now and some hints as to how we'll do this in the future.
Technique #1
If you want to know if finalization is a problem any regular profiler can give you a good hint. Using CLR Profiler for instance you can run your application then use the Call Tree view and select the various tabs until you find the one for the finalizer thread. It's very obvious which one that is because all the method names will be things like "SomeClass::Finalize". A lot of times just knowing which classes are being finalized a lot is enough to fix the problem.
Technique #2
If you see a lot of finalization but the finalizer is associated with a class or classes for which there are many instances and you want to know which instance is the bad one there is more you can do. If the class is one you control you can call store a stacktrace in the object when it is created with "StackTrace(Thread targetThread, bool needFileInfo)" (note this is a heavyweight thing to do, not something you want to ship with) then when the object is finalized you can dump that stack trace so that you will see what the stacktrace created the object and you can target that trace for elimination. To keep the cost down you might take a stacktrace at random only 1/1000th of the time and you could still get enough samples of the bad pattern to identify it over a long run.
Technique #3
If you don't own the class then its harder but you still might be able to make some headway. The first thing you need is a durable way to identify a particular instance of a particular class -- you can't use an object pointer for this because the object can move over time but for many classes (almost all reference types) you can hash the class using GetHashCode() and the hash doesn't change over the life of the object. Hash the objects you care about as soon as you can and capture the best callstack you can to finger their creator (see above). Next use your favorite debugger to stop in the Finalizer for the classes in question -- once you are stopped there dump the object's hash code in the debugger -- you should be able to put a call to GetHashCode() in the debugger watch window for instance. You can use that hash code to find the callstack that created the object. If the callstack is uninteresting or too expensive you can use any other friendly description of the callsite that will help you diagnose the originating location.
Looking forward:
The profiling API (the one that CLR Profiler uses for instance) in V1.0 and V1.1 provides almost enough information to do this job more automatically. In V2.0 (Whidbey) we're doing more:
ICorProfilerCallback2 adds (among other things) this little gem:
virtual HRESULT STDMETHODCALLTYPE FinalizeableObjectQueued(
/* [in] */ DWORD finalizerFlags,
/* [in] */ ObjectID objectID) = 0;
This fellow tells profilers directly when an object is queued for finalization. The object allocation notifications already tell whenever an object is allocated and of course you can get the stack (CLR Profiler does this today). Using the notifications for allocations and for object motion you can easily track the object pointer over its lifetime and when it becomes finalized you can identify the stack that created it. This one function was the missing piece. But even without it, the three techniques above often give good results.
Comments
- Anonymous
April 17, 2005
Thanks for the article, Rico - great stuff.
If you own the code, couldn't you also put a Debug.Assert(false) in the finalizer? Or at least a Log.Warn? - Anonymous
April 17, 2005
You could do that but it wouldn't give you much of a clue who it was that allocated the object. The tough problem happens when there are many allocations and only some of them are not getting disposed. - Anonymous
April 21, 2005
One Microsoft blog I have found particularly useful is Rico Mariani’s Performance Tidbits.  Rico... - Anonymous
April 29, 2005
Great article! I like hearing about all the cool tools we have at our disposable, but I love even more to hear specific instructions for how to use them effectively. Thanks a lot!
In the last part, you mention that version 2.0 of the CLR adds a hook for profiling queuing of finalizable objects. Does this mean there will also be a new version of the CLR Profiler that tracks this? - Anonymous
April 29, 2005
The comment has been removed - Anonymous
May 12, 2005
You mention that StackTrace is "heavyweight" -- can you characterize this more please? We were thinking about manually grabbing a StackTrace for the logging of errors (when we don't already have one available in an Exception), but we are nervious about potential performance impact. - Anonymous
May 13, 2005
Stacks are potentially very deep and so crawling one might be more than a wee bit of work. Even a fairly short stack involves a good bit of analysis to find and record all the frames. And then there's all the touching of metadata to get nice symbol names when you want those.
It's not an insignificant amount of work. I definately recommend you measure the impact to see if it's a good value for your application. Of course I say the same thing about integer math so, y'know :) - Anonymous
May 03, 2007
PingBack from http://msnet.yurx.com/2007/05/04/detecting-leaks-in-the-application/