Monday, January 12, 2009

Detecting A Managed Memory Leak

What is a Memory Leak?
In Computer Science, “Memory Leak” is a particular type of unintentional memory consumption by a computer program which occurs when the program fails to release memory that is no longer needed. Usually, this condition arises due to a bug in the program which prevents it from freeing up memory that it no longer needs.

Does traditional memory leak exist in .Net?
.Net CLR does an excellent job of reclaiming the memory that it has assigned to a program on the request. So, in theory if a memory has been assigned by .Net CLR, it can reclaim this memory by cleaning up our live references. However, we can have a native leak in the following cases:
• Improper use of Interops
• Unsafe code
• GCHandles
• Failure to dispose the objects that internally use native resources

There are many more reasons, but those are not so common.

Memory Management by .Net CLR
Though .Net CLR manages the memory in an excellent way, it does not take into account any unintentional leak done by a programmer. The “unintentional leak” here means that the programmer fails to remove object references for an object instance that should not be alive anymore.

Code Snippet
Let us write a simple application where supposedly the programmer wanted to put some data in a hashtable data structure and remove the item from hashtable when the task for that item was over. Apparently, the programmer forgot to write the code to remove items from one of the code flow paths.


public partial class Form1 : Form
{
private Hashtable hshDataHolder = new Hashtable();
public Form1()
{
InitializeComponent();
}

private void btnLeak_Click(object sender, EventArgs e)
{
for (int i=0; i<50;i++)
{
byte[] _data = new byte[1024*1024];
hshDataHolder.Add(DateTime.Now.Ticks + i,_data);
Thread.Sleep(1);
}
}
}

In this program, things will work fine for a while, as our programmer friend wrote correct business logic to process the data stored in hashtable. However, over a period of time, the memory (specifically, the Virtual memory) consumption by the application shoots up as the data items that should have been removed start piling up.

Detecting a Managed Memory Leak
Code Review
One of the best practices in programming is to have a thorough code review. To an experienced eye, detecting a memory leak is easy if the code footprint is small.

WinDbg tool
However, in the case where lines of code run into tens of thousands, it is next to impossible to detect memory leak just by manual review process. This is especially the case if you are not the author of the program 

Using WinDbg, a code debugging tool, we are better equipped to find a memory leak.

WinDbg can be downloaded from Microsoft web site at: http://www.microsoft.com/whdc/devtools/debugging/installx86.Mspx.

The tool needs symbols files for the operating system, which can be downloaded at: http://www.microsoft.com/whdc/DevTools/Debugging/symbolpkg.mspx

Here are the steps to use WinDbg:
1. Run the application to be debugged for memory leak.

2. Make sure the application has reached a stage where it has consumed more memory than normally expected (hence, suffering from a ‘memory leak’).

3. Open WinDbg and attach the debugger to the application.

4. In the command box, type
.loadby sos mscorwks and press Enter. This loads the managed debugging extension SOS (Son Of Strike). This extension provides useful statistics about the managed code.

5. Run the command
!DumpHeap –stat
This command provides memory usage of each class type in the application. The output reads something like this:


--------------------------------------------------------------------------------------
MT Count Size Type
--------------------------------------------------------------------------------------
0b223718 18 720 System.Windows.Forms.Internal.IntNativeMethods+LOGFONT
7ae3c59c 17 748 System.Drawing.BufferedGraphics
79332f40 27 1512 System.Collections.Hashtable
7932b16c 68 1632 System.Collections.Stack
79331a6c 84 1680 System.RuntimeType
7933151c 17 1700 System.Char[]
79330508 143 1716 System.Object
79332a88 20 2032 System.Int32[]
79332178 150 2400 System.Int64
7b22394c 68 4896 System.Windows.Forms.Internal.DeviceContext
648c8c4c 95 5320 System.Configuration.FactoryRecord
7933303c 27 10200 System.Collections.Hashtable+bucket[]
793040bc 120 37832 System.Object[]
793308ec 832 69552 System.String
0015c5c8 675 181748 Free
7933335c 153 157299228 System.Byte[]
Total 3217 objects

6. We see that about 153 MB of memory is taken up by System.Byte[]! That’s a good starting point for our memory leak detection process. Next, let us inspect exactly at which point this memory is consumed by the application.

7. Run
!DumpHeap -type System.Byte[]
This command displays all the instances of System.Byte[] created by the application, along with their memory pointers and size of the objects.


--------------------------------------------------------------------------------------
Address MT Size
--------------------------------------------------------------------------------------
0b391110 7933335c 1048592
0b491130 7933335c 1048592
0b591150 7933335c 1048592
0b691170 7933335c 1048592
0b791190 7933335c 1048592
0b8911b0 7933335c 1048592
0ba91000 7933335c 1048592
0bb91020 7933335c 1048592
0bc91040 7933335c 1048592
0bd91060 7933335c 1048592
0be91080 7933335c 1048592
0bf910a0 7933335c 1048592
0c0910c0 7933335c 1048592
0c1910e0 7933335c 1048592
0c291100 7933335c 1048592
0c391120 7933335c 1048592
0c491140 7933335c 1048592
0c591160 7933335c 1048592
0c691180 7933335c 1048592
0c7911a0 7933335c 1048592
0c8911c0 7933335c 1048592
total 153 objects
Statistics:
MT Count TotalSize Class Name
7933335c 153 157299228 System.Byte[]
Total 153 objects

8. We observe that many byte[] of size about 1 MB are stored somewhere by the application. Let us check if they are live (referenced) instances, and if they are, where they are referenced.

9. Run
!GCRoot 0bc91040
Here 0bc91040 is the memory address of any one of the byte[] instance that we found in the previous step. Display shows an output similar to the following:


0:004> !GCRoot 0bc91040
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
eax:Root:0128a4ec(WindowsApplication2.Form1)->
0128a638(System.Collections.Hashtable)->
012c1bd4(System.Collections.Hashtable+bucket[])
Scan Thread 0 OSTHread c6c
Scan Thread 2 OSTHread 8fc

10. Here we can see that the byte[] is referenced (live) and it is stored in a hashtable. (Think this could have been found without looking into the code?). The hashtable is created in Form1 class. This limits our scope further, as now we know that we have to look for a hashtable which is declared in Form1 class which stores a byte[].

11. Let us dump the object Form1 (that stores the hashtable in question) by the command
!do 0128a4ec
Here 0128a4ec is the address of Form1

Display shows output similar to the following:


--------------------------------------------------------------------------------------
MT Field Offset Type VT Attr Value Name
--------------------------------------------------------------------------------------
79332b38 4001e82 fe0 System.Int32 1 static 58 PropMainMenuStrip
79332b38 4001e83 fe4 System.Int32 1 static 59 PropMdiWindowListStrip
79332b38 4001e84 fe8 System.Int32 1 static 60 PropMdiControlStrip
79332b38 4001e85 fec System.Int32 1 static 61 PropSecurityTip
79332b38 4001e86 ff0 System.Int32 1 static 62 PropOpacity
79332b38 4001e87 ff4 System.Int32 1 static 63 PropTransparencyKey
79330508 4001e9a bc0 System.Object 0 static 0128aa04 EVENT_MAXIMIZEDBOUNDSCHANGED
00000000 4000004 13c 0 instance 00000000 components
7b21a924 4000005 140 ...dows.Forms.Button 0 instance 012a8f14 btnLeak
79332f40 4000006 144 ...ections.Hashtable 0 instance 0128a638 hshDataHolder

12. Studying the above output, we ascertain the name of the hashtable as hshDataHolder.

This definitely makes it easier to find the related code! Next, it is up to the programmer to refine and fine-tune the lines of code in order to get rid of the memory leak.