I first realized I was a Visual Studio weenie when I showed up to a Mono conference in Spain with a Windows laptop in a sea of Linux ones. I was so outnumbered I entertained the idea of not booting the Windows laptop at all because having the familiar Windows boot tone echoing off the conference walls would undoubtedly gather unwanted attention. Eventually, I calmed down and even managed to boot my Windows machine without too many stares.
One of the talks at the Mono conference—Mono is an open source implementation of .NET for Linux based machines—was about the progress on the Mono .NET debugger. Mono had released without a debugger initially and the debugger quickly become the #1 requested feature, no doubt mostly from .NET Windows users looking to port their .NET apps to Linux. The debugger being worked on, the presenter informed us, would not just be a command line debugger like GDB but would be fully integrated into MonoDevelop—the Linux equivalent of a Visual Studio—so that breakpoints could be set next to the source code. He even had a cute demo that worked. This is nice, I thought. After all, who needs a complicated command line debugger that takes years to learn. I needed to get things done.
During the many breaks we had between presentations, I was able to sit next to a few of the Mono developers and watch them code. They were very efficient with GDB and VI—the command line debugger and editor of choice for any self respecting Linux hacker. Still I figured, I was just as productive in Visual Studio.
And for the most part I am, except for nasty programming realities like memory leaks. For those, Visual Studio was at a loss and I was too until I discovered Windbg, the command line debugger for Windows, and its extension SOS. After learning a handful of commands, I was impressed in how quickly I was able to track down the source of leaks that had eluded fellow Visual Studio zealots. Maybe those Mono guys were on to something. After all, nothing says I am a debugger extraordinaire quicker than opening up Windbg and pounding out a few commands in front of your fellow programmers.
Below is a few commands and a tutorial on tracking down a leak in an example program.
!dumpheap -stat Displays every managed type on the heap
!dumpheap -type typename Displays every managed type that matches the specified type
!dumpheap -mt methodtable Displays every instance of the specified method table
!gcroot address Shows reference chain
!dumpobj address Shows fields and addresses of references
!dumparray address Shows objects in an array
!dumpmd Dumps the method description
!u Show assembly code
g Continue, let the program run
Ctrl+Break Break into the Windbg debugger
!help [command] Show help
The .NET Windows Forms example program I wrote draws circles on a form. The circles are placed in random locations and move from top to bottom until they vanish. There are 500 circles on the form at any time. The program works well except it leaks memory like the Titanic. It went from 10MB to 20MB in 30 seconds and climbs into the hundreds of megs if left to run.

The code is below. See if you can spot the issue before we run Windbg.
public partial class Form1 : Form
{
private const int NumberOfCircles = 500;
private Timer _timer;
public event EventHandler<DrawCircleEventArgs> Draw;
public Form1()
{
InitializeComponent();
_timer = new Timer();
_timer.Interval = 100;
_timer.Start();
_timer.Tick += new EventHandler(OnTick);
InitCircles();
}
private void InitCircles()
{
for (int x = 0; x < NumberOfCircles; x++)
AddCircle();
}
private void AddCircle()
{
Circle circle = new Circle(new Rectangle(0, 0, Width, Height));
circle.Done += new EventHandler(OnDone);
Draw += new EventHandler<DrawCircleEventArgs>(circle.Draw);
}
private void OnTick(object sender, EventArgs e)
{
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
if (Draw != null)
Draw(this, new DrawCircleEventArgs(e.Graphics));
}
private void OnDone(object sender, EventArgs e)
{
AddCircle();
}
}
public class Circle
{
private static Random Rand;
private Rectangle _parentBounds;
private Point _position;
private Size _circleSize;
private byte[] _bytes;
private bool _done;
public event EventHandler Done;
static Circle()
{
Rand = new Random((int)DateTime.Now.Ticks);
}
public Circle(Rectangle parentBounds)
{
_bytes = new byte[10240]; //10K for no reason but to make object bigger
_done = false;
_parentBounds = parentBounds;
int xPosition = Rand.Next(parentBounds.Left, parentBounds.Right);
int yPosition = Rand.Next(parentBounds.Top, parentBounds.Bottom);
_position = new Point(xPosition, yPosition);
_circleSize = new Size(10, 10);
}
private void OnDone()
{
_done = true;
if (Done != null)
Done(this, EventArgs.Empty);
}
public void Draw(object sender, DrawCircleEventArgs args)
{
_position = new Point(_position.X, _position.Y + 1);
Rectangle rect = new Rectangle(_position, _circleSize);
if (!_parentBounds.Contains(rect) && !_done)
OnDone();
else
args.Graphics.DrawEllipse(Pens.Green, rect);
}
}
To find the leak, I will open Windbg and attach to the program using File>>Attach to Process. If you don’t have Windbg, download the debugging tools for windows here. Next, load SOS like:
.loadby sos mscorwks
With SOS loaded, I can run commands from above like:
!dumpheap -stat
658551a8 163 3912 System.Windows.Forms.InvalidateEventArgs
641b8c4c 95 5320 System.Configuration.FactoryRecord
6585394c 78 5616 System.Windows.Forms.Internal.DeviceContext
6a72a930 190 6840 System.Collections.Hashtable+HashtableEnumerator
6a73303c 28 7608 System.Collections.Hashtable+bucket[]
6a7308ec 812 61060 System.String
6a7040bc 153 63636 System.Object[]
6a729b58 4804 153728 System.EventHandler
00126ba8 4847 155104 System.EventHandler`1[[Leaky.DrawCircleEventArgs, Leaky]]
00126ac4 4725 245700 Leaky.Circle
0032c6b0 1754 279984 Free
6a73335c 4729 48451204 System.Byte[]
This shows all types on the heap, number of instances, and the sizes with the largest on the bottom. I can see the byte arrays are taking 48 Megs and Leaky.Circle is 245k. The large byte arrays are a field of the Circle class designed to show the leak easier. What is more interesting is there are 4725 instances of the circle class. I was expecting only around 500 because as the circles go outside the form they should be garbage collected. So what is causing these Circle instances to stay in memory? I dump the method table listed for the Circle like:
!dumpheap -mt 00126ac4
071deb54 00126ac4 52
071e13f4 00126ac4 52
071e40fc 00126ac4 52
071e699c 00126ac4 52
071e923c 00126ac4 52
071ebadc 00126ac4 52
071ee37c 00126ac4 52
This shows all the instances of that method table on the heap. I can now use !gcroot on one of the addresses like:
!gcroot 071e13f4
ebx:Root:01c842b4(System.Windows.Forms.Application+ThreadContext)->
01c83878(Leaky.Form1)->
071f0bfc(System.EventHandler`1[[Leaky.DrawCircleEventArgs, Leaky]])->
064c5b38(System.Object[])->
071e3c54(System.EventHandler`1[[Leaky.DrawCircleEventArgs, Leaky]])->
071e13f4(Leaky.Circle)
This Circle instance is being held by an event handler which is being tracked back to the form itself. Let’s examine why the event handler above is holding onto the Circle instance by using the dump object command.
!dumpobj 071e3c54
Name: System.EventHandler`1[[Leaky.DrawCircleEventArgs, Leaky]]
MethodTable: 00126ba8
EEClass: 6a4c3518
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
6a730508 40000ff 4 System.Object 0 instance 071e13f4 _target
6a72fd60 4000100 8 ...ection.MethodBase 0 instance 00000000 _methodBase
6a7331b4 4000101 c System.IntPtr 1 instance 12c118 _methodPtr
6a7331b4 4000102 10 System.IntPtr 1 instance 0 _methodPtrAux
6a730508 400010c 14 System.Object 0 instance 00000000 _invocationList
6a7331b4 400010d 18 System.IntPtr 1 instance 0 _invocationCount
Finding the method this event handler is pointing to is a bit tricky. First, we dump the raw memory for the _methodPtr field
dd 12c118
0012c118 1c4653e9 00005f00 00126aac 00000000
0012c128 00000000 00000000 00000000 00000000
0012c138 00000000 00000000 00000000 00000000
0012c148 00000000 00000000 00000000 00000000
0012c158 00000000 00000000 00000000 00000000
0012c168 00000000 00000000 00000000 00000000
0012c178 00000000 00000000 00000000 00000000
0012c188 00000000 00000000 00000000 00000000
Then we dump the method description who’s address is located on the first line fourth column
!dumpmd 00126aac
Method Name: Leaky.Circle.Draw(System.Object, Leaky.DrawCircleEventArgs)
Class: 008f0b28
MethodTable: 00126ac4
mdToken: 06000014
Module: 00122c5c
IsJitted: yes
CodeAddr: 002f0770
The event handler is pointing to the Draw method on the Circle and keeping it in memory. I must not be unhooking the event. Looking at the source code, it becomes apparent that I am not unhooking that method anywhere in the code. The proper place to do it would be in the OnDone method of the Form1 class.
private void OnDone(object sender, EventArgs e)
{
Circle circle = sender as Circle;
Draw -= new EventHandler<DrawCircleEventArgs>(circle.Draw);
AddCircle();
}
Now if I run the same test as before and dump the heap I get:
!dumpheap -stat
6a72b16c 245 5880 System.Collections.Stack
6a73303c 28 7608 System.Collections.Hashtable+bucket[]
6585394c 245 17640 System.Windows.Forms.Internal.DeviceContext
6a729b58 1193 38176 System.EventHandler
00146ba8 1230 39360 System.EventHandler`1[[Leaky.DrawCircleEventArgs, Leaky]]
6a7308ec 755 59232 System.String
00146ac4 1176 61152 Leaky.Circle
6a7040bc 315 95188 System.Object[]
0035c6b0 918 1738620 Free
6a73335c 1180 12066856 System.Byte[]
Notice there is a smaller number of circles and byte arrays and when I run for a long time the memory doesn’t go over 30 megs. There are 1176 circles but most of those are waiting to be collected. This may seem like a contrived example but from my experience it is all too real. Most memory leaks I have found in code bases are because of event handlers not being unhooked.