Mike Stall posted some information on the CLR Profiler in 2.0, which reminded me of something I struggled with last week.

I wanted to track down a GC Handle leak, I was pretty sure someone was calling GCHandle.Alloc without calling the corresponding Free somewhere.  So I wanted to find a way to log how many GCHandles were in existance, over a series of operations.  So I searched for ways of doing this (you can totally get this kind of information information from strike, but Ben and I wanted to add automatic verification in a test).

I stumbled across this article describing .Net performance counters, the only problem was the article didn't describe how to use them.

So here is how I fished out the information, keep in mind I'm sharing my stumblings here - I'm not an expert in this area.

  1. Start->Run "perfmon"
  2. Right click on the Graph that pops up
  3. Select "Add counters..."
  4. In the "Add Counters" dialog box that appears:
    1. Select the "Performance Object" ComboBox, change to ".Net CLR Memory".  This corresponds to PerformanceCounter.CategoryName
    2. Select the "# GC Handles" in the "Select Counters From List" listbox.  This corresponds to PerformanceCounter.CounterName
    3. Select the process you want to profile.  This corresponds to PerformanceCounter.InstanceName.  Notice it doesn't have ".exe" after it...

Ok so now we know where the information comes from, lets write some leaky GCHandle code:

           PerformanceCounter pc = new PerformanceCounter();
           pc.CategoryName = ".Net CLR Memory";
           pc.CounterName = "# GC Handles";
           pc.InstanceName = System.IO.Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName);


           float startNumberOfHandles = pc.NextValue();
           GCHandle.Alloc(this);
           float endNumberOfHandles = pc.NextValue();
           MessageBox.Show((endNumberOfHandles - startNumberOfHandles).ToString());

If you're adding this to a test for some reason, you might want to make sure you call GC.Collect, GC.WaitForPendingFinalizers to make sure your handle isnt just sitting there waiting to be collected. 

If you're trying to debug other handle leaks, like User32 and GDI, I've previously written a class to show how to detect that.