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?
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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With