Memory Leaks Demo & Detection in .NET Application

Memory leaks are always headache of developers. Do .NET developers no longer bother to worry about memory leaks because of garbage collection? Yes and NO. GC periodically find objects that cannot be accessed in the future and then reclaim the resources used by the objects. GC achieves this by maintaining a list of references to live objects. When this mechanism is broken, memory leak happens.

There are many reasons to leak memory. In addition to calling unmanaged code from managed code, another one of general cases is about event handler. If you do this:

     Foo.FooEvent += new EventHandler(MemoryLeaksHere.Method);

When you complete using MemoryLeaksHere, but you are still using Foo, then MemoryLeaksHere will still remain alive as well. MemoryLeaksHere object will leak memory as a result of failing to GC.

Let us take a look at one simple example first.

using System;

namespace MemoryLeakSample

{

    class Foo

    {

        public static Foo myFoo;

        public event EventHandler FooEvent;

        public Foo()

        {

            myFoo = this;

        }

        public void FooMethod()

        {

            MemoryLeaksHere memLeak = new MemoryLeaksHere();

            memLeak.TryQuit();

        }

        public void FireEvent()

        {

            FooEvent(null, null);

        }

        static void Main(string[] args)

        {

            Foo foo = new Foo();

            for (int i = 0; i < 5; ++i)

      {

                foo.FooMethod();

            }

            GC.Collect();

            GC.WaitForPendingFinalizers();

            GC.Collect();

            Console.WriteLine("Check memory leak here.");

        }

    }

    /// <summary>

    /// This object will cause memory leak

    /// </summary>

    public class MemoryLeaksHere

    {

        public MemoryLeaksHere()

        {

            Foo.myFoo.FooEvent += new EventHandler(OnMyFooEventFired);

            Console.WriteLine("\nObject-{0}: Construct. Subscribe.", this.GetHashCode());

        }

        ~MemoryLeaksHere()

        {

            Console.WriteLine("Object-{0}: Deconstruct.", this.GetHashCode());

        }

        public void TryQuit()

        {

            Console.Write("Object-{0}: leak me?", this.GetHashCode());

            string input = Console.ReadLine();

            if (string.Equals(input, "no"))

            {

                Foo.myFoo.FooEvent -= new EventHandler(OnMyFooEventFired);

                Console.WriteLine("Object-{0}: Unsubscribe.", this.GetHashCode());

            }

            else

            {

                Console.WriteLine("Object-{0}: Not Unsubscribe", this.GetHashCode());

            }

        }

        private void OnMyFooEventFired(object sender, EventArgs e)

        {

            // Do something

        }

    }

}

In MemoryLeaksHere object’s constructor, Foo starts to hold a reference to MemoryLeaksHere by registering event handler. In MemoryLeaksHere.TryQuit(), if we don't unregister, memory leak will happen.

To be more intuitive, you can copy/paste sample code to VS2008, and then enable unmanged code debugging by following:

Project->Properties->Debug->Enable Unmanaged Code debugging

Now set a breakpoint at “Check memory leak here”, and start build/debug. When being asked leak me or not, you can choose either yes or no. For example:

 

Here, looks like we leak two of them. Finally app will hit the breakpoint and stop. At this point, we can go to VS immedate window to load sos.dll, and then check how many objects in the heap:

!load sos.dll

extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded

!dumpheap -type MemoryLeaksHere

PDB symbol for mscorwks.dll not loaded

 Address MT Size

0132e7d0 00983104 12

0132eba0 00983104 12

total 2 objects

Statistics:

      MT Count TotalSize Class Name

00983104 2 24 MemoryLeakSample.MemoryLeaksHere

Total 2 objects

So now we know there are two object instances are not recycled. Why are they not GC-ed? Because someone has a reference to them. Choose one of them, and use gcroot command.

!gcroot 0132e7d0

Note: Roots found on stacks may be false positives. Run "!help gcroot" for

more info.

Error during command: Warning. Extension is using a callback which Visual Studio does not implement.

Scan Thread 7592 OSTHread 1da8

ESP:12f434:Root:01312d48(MemoryLeakSample.Foo)->

0132f704(System.EventHandler)->

0132f6ec(System.Object[])->

0132e7dc(System.EventHandler)->

0132e7d0(MemoryLeakSample.MemoryLeaksHere)

Scan Thread 4704 OSTHread 1260

Now we can see that MemoryLeakSample.Foo is still referencing MemoryLeakSample.MemoryLeaksHere via event handler. If it is not 5 iterations, image what would happen if every incoming request results in a slice of memory leak... Soon or later, you online service will be down.

See also:

https://www.codeproject.com/KB/dotnet/Memory_Leak_Detection.aspx

https://blogs.msdn.com/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx

https://blogs.msdn.com/calvin_hsia/archive/2008/04/11/8381838.aspx

https://www.automatedqa.com/techpapers/net_allocation_profiler.asp

https://blogs.msdn.com/greg_schechter/archive/2004/05/27/143605.aspx