Tuesday, March 31, 2009

.Net Memory Leaks. It IS possible!

Let's face it; us .NET Developers have it pretty good these days. Ask any seasoned C++ programmer, and they'll undoubtedly have a few horror stories about an app they once worked on which had a memory leak somewhere someplace, and how hard it was to track down. When working in an unmanaged environment, this is part of the deal. Memory management is in your hands. When working in a managed environment, you just sit back and let the Garbage Collector do the dirty work. Therefore, it can be easy to simply assume that when coding in .NET you don't need to worry about memory leaks ever. Well, that's not entirely true as I'll demonstrate here.

The premise is simple. If you have an object, we'll call it Foo, and this object holds a reference to an object that we'll call Bar. Now, Bar, has an event that can be raised at any time. Our Foo object registers for this event. Now, when our Foo object goes out of scope, and we lose a reference to it, if we DIDN'T unhook from the Bar event, Bar is still holding a reference to this object, and the Garbage Collector can't claim that memory back. We however, no longer have a reference to it directly, which now result in a nasty memory leak. Here's a simple demo app:

First, let's create a class that raises an event. We'll call is EventRaiser:

    public class EventRaiser
    {
        private string stringValue;
 
        public string StringValue
        {
            get { return this.stringValue; }
            set
            {
                if (this.stringValue != value)
                {
                    this.stringValue = value;
                    if (this.StringValueChanged != null)
                    {
                        this.StringValueChanged(this, new EventArgs());
                    }
                }
            }
        }
 
        public event EventHandler StringValueChanged;
    }


This class is nothing special, it just has a property called StringValue. Every time StringValue is changed, we raise our StringValueChanged event.

Now, let's create a class that will have a reference to an EventRaiser object and will register for the StringValueChanged event:

    public class MemoryLeak
    {
        private byte[] allocatedMemory;
        private EventRaiser raiser;
 
        public MemoryLeak(EventRaiser raiser)
        {
            this.raiser = raiser;
            //allocate some memory to mimic a real life object
            this.allocatedMemory = new byte[10000]; 
            raiser.StringValueChanged += new EventHandler(raiser_StringValueChanged);
        }
 
        private void raiser_StringValueChanged(object sender, EventArgs e)
        {
 
        }
    }


Here we create a MemoryLeak class. This class takes as a parameter in the constructor an object of type EventRaiser. It then allocates a large array of bytes, just to make the object a larger object in memory. It then hooks up to the EventRaiser's StringValueChanged event assigning it the raiser_StringValueChanged method. This is key! We're assigning a delegate to the EventRaiser class which therefore gives our EventRaiser object a reference to this MemoryLeak object. Remember, delegates are objects like any other and can hold on to references of other objects. Therefore, the EventRaiser.StringValueChanged event (delegate) holds on to a reference of our MemoryLeak object.

Now, here's some code to demonstrate:

    public class Program
    {
        private static EventRaiser raiser;
        public static void Main(string[] args)
        {
            raiser = new EventRaiser();
            for (int i = 0; i <= 1000; i++)
            {
                CreateLeak();
            }
 
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);
        }
 
        private static void CreateLeak()
        {
            MemoryLeak memoryLeak = new MemoryLeak(raiser);
            memoryLeak = null;
            GC.Collect();
            long memory = GC.GetTotalMemory(true);
            Console.WriteLine("Memory being used: {0:0,0}", memory);
        }
    }


Simple console app that has a private field of type EventRaiser. In our main method, after we new up the EventRaiser, we call our CreateLeak method. All this method is doing is creating a new MemoryLeak object, pasing in the EventRaiser to the constructor. (At this point, the MemoryLeak will hook up to the StringValueChanged event of the EventRaiser.) Then, we assign the memoryLeak reference to null, which in a normal case would make the old MemoryLeak object subject to Garbage Collection. So, we call GC.Collect to force a collection, and then we output the amount of managed memory being used, using the GC.GetTotalMemory() method.

If you run this program, you'll notice that the amount of memory just keeps growing, and never goes down!! If you bump up the amount of bytes allocated in the MemoryLeak class, or bump up the number in the for loop in the main method, you'll get an OutOfMemoryException really quickly!

The simplest way to avoid this kind of mess, is to take advantage of the IDiposable interface. Let's add that to our MemoryLeak class and unhook from the event in the Dispose method:

    public class MemoryLeak : IDisposable
    {
        private byte[] allocatedMemory;
        private EventRaiser raiser;
 
        public MemoryLeak(EventRaiser raiser)
        {
            this.raiser = raiser;
            //allocate some memory to mimic a real life object
            this.allocatedMemory = new byte[10000]; 
            raiser.StringValueChanged += new EventHandler(raiser_StringValueChanged);
        }
 
        private void raiser_StringValueChanged(object sender, EventArgs e)
        {
 
        }
 
        public void Dispose()
        {
            this.raiser.StringValueChanged -= this.raiser_StringValueChanged;
        }
 
    }


As you can see, in the Dispose method, we just unhook from the EventRaiser's StringValueChanged event. Now, we'll modify our main method to actually call the Dispose method:

        private static void CreateLeak()
        {
            MemoryLeak memoryLeak = new MemoryLeak(raiser);
            memoryLeak.Dispose();
            memoryLeak = null;
            GC.Collect();
            long memory = GC.GetTotalMemory(true);
            Console.WriteLine("Memory being used: {0:0,0}", memory);
        }


Before we assign memoryLeak to null, we just call the Dispose method (or you can wrap the MemoryLeak assignment in a using statement). Now, run the program and what do you know?! The total memory being used stays the same throughout! Crisis averted.

So, to anyone that thinks memory management in a managed world isn't important, think again. Yes, you don't activley have to play a role in the management of memory in your app, but you DO need to be conscious about it and understand what's going on.

3 comments:

Unknown said...

Great article. I'll definitely be sure to keep that in mind when dealing with events.

A.Friedman said...

Thanks! It's something you should definitley keep in the back of your mind when working with events.

Andrea said...

thanks for this article, well done.