16

I have an application which has some memory leaks due to events not being detached before an object reference is set to null. The applicaiton is quite big and its difficult to find the memory leaks by looking at the code. I want to use sos.dll to find the names of the methods that are the source of leaks but I'm getting stuck. I set up a test project to demonstrate the problem.

Here I have 2 classes, one with an event, and on the listens to that event as below

namespace MemoryLeak
{
    class Program
    {
        static void Main(string[] args)
        {
            TestMemoryLeak testMemoryLeak = new TestMemoryLeak();

            while (!Console.ReadKey().Key.Equals('q'))
            {
            }
        }
    }

    class TestMemoryLeak
    {
        public event EventHandler AnEvent;

        internal TestMemoryLeak()
        {
            AnEventListener leak = new AnEventListener();
            this.AnEvent += (s, e) => leak.OnLeak();
            AnEvent(this, EventArgs.Empty);
        }

    }

    class AnEventListener
    {
        public void OnLeak()
        {
            Console.WriteLine("Leak Event");
        }
    }
}

I break into the code, and in the intermediate window type

.load sos.dll

then I use !dumpheap to get the objects on the heap of the type AnEventListener

!dumpheap -type MemoryLeak.AnEventListener

and I get the following

PDB symbol for mscorwks.dll not loaded
 Address       MT     Size
01e19254 0040348c       12     
total 1 objects
Statistics:
      MT    Count    TotalSize Class Name
0040348c        1           12 MemoryLeak.AnEventListener
Total 1 objects

I use !gcroot to work out why the object is not being garbage collected

!gcroot 01e19254

and get the following

!gcroot 01e19254
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 5208 OSTHread 1458
ESP:2ef3cc:Root:01e19230(MemoryLeak.TestMemoryLeak)->
01e19260(System.EventHandler)->
01e19248(MemoryLeak.TestMemoryLeak+<>c__DisplayClass1)->
01e19254(MemoryLeak.AnEventListener)
Scan Thread 7376 OSTHread 1cd0

I can now see the event handler that is the source of the leak. I use !do to look at the fields of the event handler and get

!do 01e19260
Name: System.EventHandler
MethodTable: 65129dc0
EEClass: 64ec39d0
Size: 32(0x20) bytes
   (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
65130770  40000ff        4        System.Object  0 instance 01e19248 _target
6512ffc8  4000100        8 ...ection.MethodBase  0 instance 00000000 _methodBase
6513341c  4000101        c        System.IntPtr  1 instance 0040C060 _methodPtr
6513341c  4000102       10        System.IntPtr  1 instance 00000000 _methodPtrAux
65130770  400010c       14        System.Object  0 instance 00000000 _invocationList
6513341c  400010d       18        System.IntPtr  1 instance 00000000 _invocationCount

So now I can see the pointer to the method that is not being detached

0040C060 _methodPtr

but how do I get the name of that method?

Gaz
  • 3,855
  • 6
  • 28
  • 33
  • 1
    Please see my answer to this question for how to get what _methodPtr is pointing to http://stackoverflow.com/questions/3668642/get-method-name-form-delegate-with-windbg/3682594#3682594 – Brian Rasmussen Nov 20 '10 at 18:15
  • 2
    Wow, this is a pretty thorough explanation of a debugging examination. Upvote for taking the time to post a detailed example - and it may come in use to others in future. I'll bookmark this. – Tom W Nov 20 '10 at 19:40

5 Answers5

4

Events are tricky because when A subscribes to B, both end up holding a reference to each other. In your example, this is not a problem as there is no leak (A created B and is the only object to hold a reference to B, so both A and B will die when A dies).

For real event problems, what would solve it is the concept of "weak events". Unfortunetely, the only way to get 100% working weak events is with support from the CLR. Microsoft appears to have no interest in providing this support.

I recommend you google "weak events in C#" and start reading. You will find many different approaches to solving the problem, but you must be aware of their limitations. There is no 100% solution.

Tergiver
  • 14,171
  • 3
  • 41
  • 68
  • Hey Tergiver, thanks for the info. I'll have a look at weak events for future reference.The issue with the application I'm working on is that there is a coordinating class that is around for the life time of the application. And it is the events on this class that are causing the memory leaks, as any subscribers are being keep alive by reference to the events on this coordinating class. – Gaz Nov 20 '10 at 19:30
  • When dealing with static events without a weak event pattern, the only option you have is to unsubscribe before your last reference disappears (after which point you have no means to unsubscribe). – Tergiver Nov 20 '10 at 19:53
  • I should say, "last *visible* reference". – Tergiver Nov 20 '10 at 19:55
1

What about implementing the good old IDisposable ?

        class TestMemoryLeak : IDisposable
        {
              public event EventHandler AnEvent;
              private bool disposed = false;

           internal TestMemoryLeak()
           {
                 AnEventListener leak = new AnEventListener();
                 this.AnEvent += (s, e) => leak.OnLeak();
                AnEvent(this, EventArgs.Empty);
           }

           protected virtual void Dispose(bool disposing)
           {
              if (!disposed)
               {
                 if (disposing)
                 {
                      this.AnEvent -= (s, e) => leak.OnLeak();
                 }
                 this.disposed = true;
                }

            }

           public void Dispose() 
           {
                this.Dispose(true);
                GC.SupressFinalize(this);
           }

    }
dexter
  • 7,063
  • 9
  • 54
  • 71
  • Hi Max, thanks for the reply. That would be cool in this example, but in the real application, there is a lot of code and I don't know that name of the method that needs detaching, so I'm trying to find that out... – Gaz Nov 20 '10 at 18:15
  • @Gaz: But you could use this to enumerate the delegates attached to the event, and examine the `Method` property of each of those delegates in order to find out who's not detaching. – Jim Mischel Nov 20 '10 at 20:03
1

I would recommend using a .NET memory profiler to get to the heart of the matter, there are several out there - personally I have used the Red Gate ANTS memory profiler in the past which does have a 14 day free trial:

http://www.red-gate.com/products/ants_memory_profiler/walkthrough.htm

BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
1

Expanding on the IDisposable idea that @Max Malygin proposed:

The code below shows how to check for outstanding handlers on an event.

The class has a Tick event that fires once per second. When Dispose is called, the code enumerates the handlers in the invocation list (if there are any), and outputs the class and method name that's still subscribed to the event.

The program instantiates an object, attaches an event handler that writes "tick" every time the event is fired, and then sleeps for 5 seconds. It then disposes the object without un-subscribing the event handler.

using System;
using System.Diagnostics;
using System.Threading;

namespace testo
{
    public class MyEventThing : IDisposable
    {
        public event EventHandler Tick;
        private Timer t;

        public MyEventThing()
        {
            t = new Timer((s) => { OnTick(new EventArgs()); }, null, 1000, 1000);
        }

        protected void OnTick(EventArgs e)
        {
            if (Tick != null)
            {
                Tick(this, e);
            }
        }

        ~MyEventThing()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private bool disposed = false;
        private void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    t.Dispose();
                    // Check to see if there are any outstanding event handlers
                    CheckHandlers();
                }

                disposed = true;
            }
        }

        private void CheckHandlers()
        {
            if (Tick != null)
            {
                Console.WriteLine("Handlers still subscribed:");
                foreach (var handler in Tick.GetInvocationList())
                {
                    Console.WriteLine("{0}.{1}", handler.Method.DeclaringType, handler.Method.Name);
                }
            }
        }

    }

    class Program
    {
        static public long Time(Action proc)
        {
            Stopwatch sw = Stopwatch.StartNew();
            proc();
            return sw.ElapsedMilliseconds;
        }

        static int Main(string [] args)
        {
            DoIt();
            Console.WriteLine();
            Console.Write("Press Enter:");
            Console.ReadLine();
            return 0;
        }

        static void DoIt()
        {
            MyEventThing thing = new MyEventThing();
            thing.Tick += new EventHandler(thing_Tick);
            Thread.Sleep(5000);
            thing.Dispose();
        }

        static void thing_Tick(object sender, EventArgs e)
        {
            Console.WriteLine("tick");
        }
    }
}

The output is:

Handlers still subscribed:
testo.Program.thing_Tick
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
0

You can try like this on WinDbg

  1. Dump Target Obj to get Method Table: !dumpobj 01e19248
  2. Dump Method Table to find 0040C060 in it: !dumpmt -md 0ced1910
  3. If there no match, Dump the memory that start from the _methodPtr address: !u 0040C060
  4. Find JMP or MOVE instruction and Dump their address, eg: !u 0cf54930

Visit here for more details: http://radheyv.blogspot.com/2011/04/detecting-memory-leaks-in-silverlight.html

tungnt185
  • 51
  • 10