Memory leaks 101: Objects anchored by event generators
This problem actually comes up pretty often so I thought I'd write a little article about it, and a couple of approaches to solving it.
Basically any time you take an object "Your Object" whose life you want the GC to manage and then create a reference to it from a long lived object you’ve “anchored” the thing and now it will never go away unless you take special steps to do so. The most common case where this happens is where Your Object registers for events from some global event generator that never goes away. Now Your Object won’t go away and neither will anything that it references.
There are basically two ways to fix this, both of which are about detaching your object from that anchor:
The first approach is to make Your Object IDisposable. This works well if it has a well understood lifetime, such as a window class. When the window is (e.g.) closed the Dispose method unregisters the object from its various event sources and the whole thing goes away. This is a bit of a chore but it involves the least amount of additional object overhead. Since these leaks are easy to find (they jump right off the page using the usual techniques) you just target them for eradication and systematically fix all the window types until you don’t have a problem. Note there are often other cleanups that you want to do when a window is closed because such things often correspond to mass object extinctions.
The second approach is to create some indirections. This has the advantage that it’s automatic but it has the disadvantage of adding some complexity and non-determinism to the picture. Basically you create this picture
Event Source ----> Courier Object ----> Weak Reference ----> Your Object
Now the way this is works is that instead of registering your own object for the event, you make some kind of courier. Your Object can now go away as is normal, because the Weak Reference isn’t really holding on to it. When that happens you’ll have a dangling Courier Object. The courier in turn removes itself as a listener when it notices that the Weak Reference it is holding on to can no longer be resolved and is therefore moot. This forces you to have one Courier per object per event source. You can generalize this so that the Courier is in fact a broadcaster of sorts, which would then give you one courier per event source and you register with the courier. That’s basically the weak (broadcasting) delegate pattern.
Now as for limitations, well there are none per se but none of this stuff is free. In the second approach you allocate more memory, make more work for the collector, force more objects into generation 2 and so forth. My preference would be the first method if it is at all practical.
Sometimes folks think this is is a bug but it isn't really a CLR bug here per se: it’s a classic memory leak. If you read my blog on managed memory leaks you can see that tracking these down is basically shooting fish in a barrel. They can’t hide. There may be many of them but you just make a list and start killing them off.
So it’s your choice.
Greg Schechter gives an example of the courier approach here. In his notation
- Event Source = "Container"
- Your Object = "Containee"
- Courier Object = "WeakContainer"
https://blogs.msdn.com/greg_schechter/archive/2004/05/27/143605.aspx
Comments
Anonymous
January 09, 2007
I still think this would be better solved with a weak delegate mechanism though.Anonymous
January 09, 2007
I've commented your post here http://blog.torresdal.net/PermaLink,guid,e4b6d502-6747-47cd-9fb7-bfc00811f16c.aspxAnonymous
January 09, 2007
Can anybody tell us what was the reasoning for the design behind the strong reference model used in events in the first place?Anonymous
January 10, 2007
The comment has been removedAnonymous
January 10, 2007
Then again, if generics supported inheritance from a generic parameter, it would be almost trivial to make a flexible WeakDelegate wrapper. As it is, I suspect a little lightweight codegen could do the trick, letting the compiler do generic argument deduction to give you the exact delegate type, which you can reflect against to determine the number and types of arguments.Anonymous
January 10, 2007
You're right that weak references would make anonymous delegates problematic.Anonymous
January 10, 2007
Here are two debugging walkthroughs for finding memory leaks caused by attaching instance eventhandlers to cached and static objects http://blogs.msdn.com/tess/archive/2006/01/23/516139.aspx http://blogs.msdn.com/tess/archive/2006/11/27/asp-net-crash-bad-cacheitemremovedcallback-part-ii.aspxAnonymous
January 18, 2007
This issue bites us every day. We've tried to make our own weak reference methods - even dynamically creating the IL for the callbacks - but we can't get it anywhere near as fast as the event system, which is important to us :( But basically, almost every bit of code in our system is asynchronous, so has to be hooked up using some sort of event or callback - it wouldn't make sense to completely invert the object hierarchy, so it's almost as if we have no GC - we are back to the bad old world where we must explicitly destruct (dispose) every single object at the right time - and sometimes miss one.. even a single mistake can lock a huge tree in memory. The thing is, given that you can also use a callback or handler interface, which we do for everything that absolutely has to be listened to, I don't think there has ever been a time where I have used an event and would actually want that to lock the recipient class into memory... I think mostly when we use an event we are thinking decoupling - we are thinking A references B, but B doesn't reference A. If there was any way to change the current event system over to weak referenced, we'd be soooo happy... but even a second event paradigm would be incredibly useful! Please? :) Actually, thinking of it- there are several sort of ways of doing a weak reference based event on the net - none are super efficient, but what would be useful would be if some MS person (like you :)) - went through and created the "ideal" weak referenced event pattern. We could all start using it now, and it could just nicely flow into Orcas.