To explain how the tool detects leaks, I am using our C# application that has a subtle memory leak.
It has a Master object which communicates with his Worker objects using an event. We know there is a leak because after every time we create a Worker object and destroy it again, our memory usage increases.
Code Listing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public class Master { public event EventHandler Report; private void RaiseEvent() { EventHandler handler = Report; //publisher-subscriber idiom if (handler != null) { handler(this, new EventArgs()); } } } public class Worker { private byte[] data; public Worker(Master p) { data = new byte[10000]; p.Report += OnReport; } private void OnReport(object sender, EventArgs e) //event handler {//report status } } class Program { private static Master p = new Master(); public static void Main(string[] args) { Console.WriteLine("Do first ANTS snapshot here, then press any key to continue..."); Console.ReadKey(true); for (int i = 0; i <= 1000; i++) { Worker w = new Worker(p); UseThenDispose(w); } Console.WriteLine("Do second ANTS snapshot here, then press any key to continue..."); Console.ReadKey(true); } private static void UseThenDispose(Worker w) { Console.WriteLine("Worker doing some work ..."); w = null; //free the memory Console.WriteLine("Worker's resources are disposed."); } } |
Starting ANTS
When you open the profiler (choosing a "New Profiling Session"), you see the Startup screen:All we need to do is point it at our C# application (Worker.exe), and click "Start profiling".
The profiler starts up Worker.exe and begins collecting performance counter data:
Taking Memory Snapshots
Taking and comparing memory snapshots is a key activity when looking for memory leaks, so our approach will be as follows:
1. Wait for memory usage to stabilize, then take the first snapshot by clicking on the Take Memory Snapshot button near the top right hand corner. This first snapshot will be used as a baseline.
When we click the Take Memory Snapshot button, the memory profiler forces a full garbage collection and takes a snapshot of the memory it is using (as seen below).
1. Wait for memory usage to stabilize, then take the first snapshot by clicking on the Take Memory Snapshot button near the top right hand corner. This first snapshot will be used as a baseline.
When we click the Take Memory Snapshot button, the memory profiler forces a full garbage collection and takes a snapshot of the memory it is using (as seen below).
In our code, the memory usage stabilizes at line 32. For ease of understanding, at this juncture the code writes a message to the console window.
In our code, memory leaks have occurred by the time when we arrive at line 39. Again for ease of understanding, at this juncture the code writes a message to the console window.
If you take a careful look at the memory graph below, you can see a faint green line, which shows that the Private Bytes memory usage has increased, so take a second snapshot. The next section explains what is meant by Private Bytes.
ANTS is able to plot different types of memory usage simultaneously. Examples of the types of memory usages seen in the diagram above are as follows
- Private Bytes: Includes memory allocated (even if not in use), but excludes shared processes (e.g. DLL in memory and .NET run-time).
- Working Set: Includes shared processes as well.
3. Examine the comparison that the profiler shows us after it has finished taking and analyzing the second snapshot. This will be further explained in the next section.
Comparison of Memory Usage
The ANTS summary screen above shows a lot of information which I will explain one by one. You can follow along by looking at the octagon-shaped labels inside the summary screen.
- We can see a large memory increase between snapshots: 9.7 MB vs 52 kB.
- The largest classes are shown to us in the bottom right of the screen: Byte[] and EventHandler.
- Next, we switch to the Class List to find out more. The class list gives us a fuller picture of what's in the snapshot.
Class List
We're interested in objects which have been created since the baseline snapshot, so we can look at classes which have increased in size in the second snapshot. We therefore sort by Size Diff in decreasing order.
The Byte[] class has been placed at the top of the list, with an increase of over 10 million bytes. We want to understand why there is such a large increase, so we load the Instance Categorizer for the Byte[] class by clicking the
icon.
The Byte[] class has been placed at the top of the list, with an increase of over 10 million bytes. We want to understand why there is such a large increase, so we load the Instance Categorizer for the Byte[] class by clicking the
Instance Categorizer
We can now see the source of the leaks:
- The Byte[] arrays belong to the Worker objects
- The Worker objects were not disposed at all because the EventHandler in the Master object was still holding on to them. This is found in line 21 of the code, where the Worker objects are still subscribed to the Master object's event.
Having discovered the source, it is now easy to remove the leak. You just have to unsubscribe from the event at the time of disposal of each Worker.
Summary
There are many other features inside ANTS which I did not have to use today because I have already found the source of the leaks. If you like, you can try out ANTS yourself. There is a 2-week trial version at Redgate's website.All in all, I like the ANTS tool because it has helped me to solve hard-to-understand leaks in the C# projects that I was involved in. It is good for both C# executables and DLLs. I have also used it successfully with managed C++, since the tool supports the .NET development environment.






