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
Statistics:
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
DOMAIN(00601068):HANDLE(AsyncPinned):b15b4:Root:02bdda44(System.Threading.OverlappedData)->
02bddf38(System.Threading.IOCompletionCallback)->
02bdcfc4(System.IO.FileSystemWatcher)->
02b8b14c(System.IO.FileSystemEventHandler)->
02bdc550(ConsoleApplication1.MyWatcher)
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)
https://www.codeproject.com/KB/dotnet/Memory_Leak_Detection.aspx
https://www.julmar.com/blog/mark/PermaLink,guid,643649fc-0467-4f0d-9a95-323ed7ce4298.aspx
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
Next
GC.Collect() ' collect garbage
GC.WaitForPendingFinalizers() ' allow finalizers
GC.Collect() ' collect again for any objects that had finalizers
Debug.WriteLine("Done")
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.
MyBase.Finalize()
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
Anonymous
April 15, 2008
FileSystemWatcher has a Dispose method that should be called to release unmanaged resources. The dispose method can optionally release managed resources as well.Anonymous
April 17, 2008
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.Anonymous
April 21, 2008
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.Anonymous
April 21, 2008
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: http://msdn2.microsoft.com/en-us/library/ms686638(VS.85).aspxAnonymous
May 30, 2008
PingBack from http://jarrett.thedigestabout.info/x8664runcommandnotfound.htmlAnonymous
September 23, 2008
I have a collection of almost 30,000 pictures and videos. When I add new pictures to the collection,Anonymous
January 19, 2009
There are various leak detection methods for memory allocators. A popular one is to tag each allocationAnonymous
February 10, 2009
Memory leaks are always headache of developers. Do .NET developers no longer bother to worry about memory