Earlier today I wrote about an Assert method which can be used in unit tests to check whether or not an object is collected during Garbage Collection (GC). I wrote this method because I was suspicious about a certain PostSharp Aspect I just implemented. The aspect works great, and I’ll surely write about it later; however, since it’s an aspect which is meant to be applied on an entire assembly by using multicasting, I thought twice before calling it a day.
After being skeptical and writing unit tests to ensure my IsGarbageCollected() method was actually working as expected, I came to the unfortunate conclusion that my aspects were keeping my objects in memory!
It only makes sense really; why wouldn’t aspects be able to keep objects alive? It might not be the first thing you think about, but hopefully this post makes you aware of the hidden dangers. As an example I’ll show you a very basic aspect, and how to fix it.
[Serializable] public class SampleAspect : IInstanceScopedAspect { [NonSerialized] object _instance; public object CreateInstance( AdviceArgs adviceArgs ) { _instance = adviceArgs.Instance; return MemberwiseClone(); } public void RuntimeInitializeInstance() { } }
As any other memory leak, it suffices to leave one reference lying around to an object, preventing the GC from collecting it. There are two mistakes in the previous code, can you find them?
The first one was most obvious to me. _instance is holding a reference to the object, keeping it alive. Luckily .NET offers us an easy solution, WeakReference. A WeakReference references an object while still allowing that object to be reclaimed by garbage collection.
[Serializable] public class SampleAspect : IInstanceScopedAspect { [NonSerialized] WeakReference _instance; public object CreateInstance( AdviceArgs adviceArgs ) { _instance = new WeakReference( adviceArgs.Instance ); return MemberwiseClone(); } public void RuntimeInitializeInstance() { } }
However, Gael was kind enough to quickly point out this wasn’t the real issue. PostSharp uses prototypes to initialize its aspects. An existing prototype object is cloned to create new aspects. The CreateInstance() method is called on the prototype object, and you are expected to return a new aspect. The real error in my code was I was setting _instance on the prototype, while I should be setting it on the cloned object instead. The prototype object is kept in memory by PostSharp, and as a result the last created object was also held in memory. Not a big issue, but the following implementation is a bit cleaner.
[Serializable] public class SampleAspect : IInstanceScopedAspect { [NonSerialized] object _instance; public object CreateInstance( AdviceArgs adviceArgs ) { var newAspect = (InitializeEventHandlerAspect)MemberwiseClone(); newAspect._instance = adviceArgs.Instance; return newAspect; } public void RuntimeInitializeInstance() { } }
No need to use WeakReference any longer, although I imagine it might still be useful in other scenarios.