Examine .Net Memory Leaks

Writing programs using .Net is very productive. One reason is because much of memory management is “managed” for you. In C, C++ and other “native” languages, if you allocate memory, you’re responsible for freeing it.  There were stopgap measures, like destructors, SmartPointers and reference counting, which helped, but were still cumbersome.


Foxpro manages memory for you, and has used garbage collection for decades: Heartbeat: Garbage collection in VFP and .NET are similar


However, you can still have memory leaks in .Net.


Try this in VS 2008: (I think it will work in 2005 too, can somebody verify? Thanks)


1.       File->New->Project->VB Windows Console Application.

2.       Paste the sample code below.

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

If you’re running 64 bit, you can force 32 bit targeting: Project->Properties->Compile->Advanced Compile Settings->Target CPU->x86 (see this for more about x64)




The sample code has a loop that creates and releases an instance of a class MyWatcher that uses the FileSystemWatcher class that reacts to events, such as files being created in a directory.

The class has a member (Dim MyLargeMemoryEater(100000) As String) which eats up 4 bytes (8 bytes on x64) per array element.  At the end of each loop, the garbage collector is called to release everything. The class has a Finalize method that will be called when the garbage collector collects.


When you run the code, you see the increase in memory use in each loop. The increase per iteration is just a little more than the amount of memory used by MyLargeMemoryEater . Also, the Finalizers don’t run until the application is shutting down.


If you uncomment the “UnSubscribe” line, then the finalizer fires (on a different thread) and you can see the leak is gone.


Now let’s examine the leak by looking at the heap.


Make it leak, then put a breakpoint on the “Done” line after the loop. At this point, we suspect there are 100 instances of MyWatcher in the managed heap.


Wouldn’t it be great to see them? Let’s use SOS:


Open the Immediate window: Debug Menu->Windows-> Immediate window


Type in the lines in red


!load sos.dll

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

!dumpheap -type MyWatcher

PDB symbol for mscorwks.dll not loaded

 Address       MT     Size

02b7431c 000d313c       16    




02bdc3f0 000d313c       16    

02bdc550 000d313c       16    

total 100 objects


      MT    Count    TotalSize Class Name

000d313c      100         1600 ConsoleApplication1.MyWatcher

Total 100 objects



So now we know that there are 100 instances still around. Why were they not garbage collected? Because somebody has a reference to them. Choose one of the instances (02bdc550), and use the gcroot command:


!gcroot 02bdc550

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 6948 OSTHread 1b24

Scan Thread 536 OSTHread 218

Scan Thread 6448 OSTHread 1930







Now we see that the FileSystemWatcher is referencing us via an eventhandler.


(The “!dumpheap -stat” command is also very useful  to see what’s on the heap)





See also:

Collecting garbage at the wrong time

SOS Debugging Extension (SOS.dll)



Debugging a memory leak in managed code: Ping - SendAsync



<Code Sample>


Module Module1

    Friend g_cnt As Integer

    Sub Main()


        'uncomment these 2 lines to test the event watcher

        'Dim oFileWatcher = New MyWatcher

        'MsgBox("Wait in msgbox. FSW events still fire: copy a file into d:\")


        Dim oldPeak = 0L


        For i = 1 To 100

            Dim oWatcher = New MyWatcher


            '            oWatcher.UnSubscribe() ' uncomment this line to remove handler


            oWatcher = Nothing

            GC.Collect()    ' collect garbage

            GC.WaitForPendingFinalizers()   ' allow finalizers

            GC.Collect()    ' collect again for any objects that had finalizers

            Dim newpeak = Process.GetCurrentProcess.PeakWorkingSet64

            Debug.WriteLine("All released? " + i.ToString + " " + _

                    " WorkingSet =" + Process.GetCurrentProcess.WorkingSet64.ToString("n0") + _

                    " Peak=" + newpeak.ToString("n0") + _

                    " delta =" + (newpeak - oldPeak).ToString)

            oldPeak = newpeak



        GC.Collect()    ' collect garbage

        GC.WaitForPendingFinalizers()   ' allow finalizers

        GC.Collect()    ' collect again for any objects that had finalizers




    End Sub

End Module


Class MyWatcher

    Dim MyLargeMemoryEater(100000) As String ' make the instance bigger to magnify issue: 4 bytes per array item on x86

    Dim fsw As IO.FileSystemWatcher

    Sub New()

        fsw = New IO.FileSystemWatcher

        fsw.Path = "d:\"

        fsw.Filter = "*.*"

        AddHandler fsw.Created, AddressOf OnWatcherFileCreated


        fsw.EnableRaisingEvents = True


    End Sub

    Sub UnSubscribe()

        RemoveHandler fsw.Created, AddressOf OnWatcherFileCreated

    End Sub

    Sub OnWatcherFileCreated(ByVal sender As Object, ByVal args As System.IO.FileSystemEventArgs)

        Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + " " + args.FullPath)

    End Sub

    Protected Overrides Sub Finalize()  ' called when garbage collector collects on the GC Finalizer thread.


        Debug.WriteLine((New StackTrace).GetFrames(0).GetMethod.Name + g_cnt.ToString + " Thread= " + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString)

    End Sub



End Class


</Code Sample>

Comments (8)

  1. a reader says:

    FileSystemWatcher has a Dispose method that should be called to release unmanaged resources.  The dispose method can optionally release managed resources as well.

  2. Doug Kimzey says:

    Thank you for the really timely info.  I am currently tracking down a memory leak in an OCX being called from a VFP 9 application.  This OCX returns data to the VFP application as BSTRs through CString::AllocSysString(). Would you know if VFP handles the cleanup of BSTRs allocated in an OCX and are there any VFP calls that will invoke garbage collection to handle these BSTRs?

    Thanks again,

    Doug Kimzey

    Senior Developer

    DPRA, Inc.

  3. Greg says:

    Good to see Visual Studio 2008 making progress in adding development tools lacking in VS2003 and VS2005 (memory debugging, profiling, etc.).

    It would still be nice to get the profiler built into VS 2008 pro instead of just team foundation.

  4. Calvin_Hsia says:

    Doug: VFP Garbage collection is limited to names in the name table, not BSTRs allocated in an OCX

    Such BSTRs should follow COM Allocation rules:


  5. I have a collection of almost 30,000 pictures and videos. When I add new pictures to the collection,

  6. There are various leak detection methods for memory allocators. A popular one is to tag each allocation

  7. Bill Li says:

    Memory leaks are always headache of developers. Do .NET developers no longer bother to worry about memory

Skip to main content