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