In a previous post I talked about some great new features in the newest release (2013 Q2) release of JustTrace. The new features in that post are terrific and certainly contribute to make JustTrace an indispensable part of any .NET developer’s toolbox, but there are three features that are so cool that they are deserving of their own individual blog posts:
The Potential Binding Leaks View and the Bottlenecks View will be discussed in future posts. Today I want to show you the new Disposed Objects view.
One of the major benefits of the .NET platform is garbage collection, and as an old C++ developer, I can tell you this is a huge benefit to developers. Simply put, the garbage collector tracks object lifetimes, specifically which other objects hold references to the object. The idea is that as long as someone has a reference to an object they are potentially using it and it cannot be destroyed. Once the reference count had gone to zero, the presumption is that the object is no longer being used (it’s said to be “de-referenced”) and can be destroyed, which is exactly what the garbage collector does. This frees up memory and makes sure that there are plenty of resources for your application to run in. The important thing to remember is that this magic ONLY happens for managed resources, which essentially means .NET classes that either have been created by your or from/derived from the .NET framework.
When writing .NET applications it’s fair to say that you are going to be primarily working with managed resources, but many developers are surprised at just how many non-managed resources they use in their applications. Does your application use a database? Behind all the ORM’s and classes from System.Data are database connections, which are un-managed. Did you inherit any older functionality in the form of Win32 COM libraries? Those are the textbook definition of unmanaged resources. Are you working with the file system? System.IO is going to give you access to more un-managed resources than you can shake a stick at. These unmanaged resources are not monitored by the garbage collector, so a way is needed for the .NET classes that use them to clean them up (closing connections, destroying unused objects, freeing memory, etc.) when they are done with them.
To help with this task the .NET framework provides the IDisposable interface which contains one method called Dispose. The idea behind this interface is that the developer will put code in the Dispose method to properly clean up all of their unmanaged resources. This prevents the unmanaged resources the object holds from being orphaned. Orphaned objects hold onto memory that is not really being used by the application but cannot be freed either as the runtime does not have a valid handle to said memory. The most important thing to remember about the IDisposable interface and the Dispose method is that the runtime WILL NOT call the Dispose method for you. It is your responsibility to call Dispose on your IDisposable objects. For a more detailed explanation of IDisposable including some great advice on properly implementing the Dispose method, check out this article on MSDN.
A problem arises when a developer calls the Dispose method on an object that is then not garbage collected because there is still an object somewhere that has a handle on the object that Dispose was called on. In a large complex applications it can sometimes be difficult to know where all the references to our object are. In many cases references are created where we may not have expected or intended them to be. This can cause serious issues as the Dispose method generally puts the object in an invalid state as the unmanaged resources it was relying on no longer exist. It also signifies that the developer’s intentions are to not use the object again, but by not being garbage collected the memory the object uses is not freed. Worse yet, another object could attempt to use the new invalid “zombie” object, which could result in a fatal exception.
One of the major new features in JustTrace for 2013 Q2 is the Disposed Object view. Quite simply, when you use JustTrace to profile the memory of your .NET application it will track objects that implement the IDisposable interface that have had their Dispose method called, but have not been garbage collected.
To demonstrate this feature, I have created a simple console application that highlights one of the most common causes of issue; a forgotten or misplaced reference to an object. In this case, I’ve created several instances of of an object that implements IDisposable and added them to a List. I then iterate through the list and call Dispose on each object, but I don’t dispose of the objects by either removing them from the List or dereferencing the List itself;
1: static void Main(string args)
3: _myList = new List<MyDisposableObject>();
5: Console.WriteLine("Press any key to begin");
8: for (var idx = 0; idx < 5; idx++)
10: _myList.Add(new MyDisposableObject());
13: _myList.ToList().ForEach(x => x.Dispose());
15: Console.WriteLine("Disposed all objs, press any key to continue");
MyDisposableObject is simply an empty class with an empty Dispose method, and its internals are pretty inconsequential for this example.
This is a simple example, and in this case the List and it’s member object are de-referenced when the Main method finishes and the application ends. But in a "real” application, any logic that happened between lines 13 and 17 would have to share resources with these zombie objects. I see this particular coding issue frequently; developers assume that the garbage collector will take care of these objects eventually. The problem is in larger applications, eventually can take quite a bit longer than expected.
To demonstrate what I mean, let’s fire up JustTrace and use it to profile the memory for this application. I load the project in Visual Studio and make sure that I have selected “Memory Profiler” from the JustTrace toolbar as shown in Figure 1:
Figure 1: Selecting Memory Profiler on the JustTrace toolbar
I run the application without debugging by either hitting Ctrl+F5 or selecting “Start Without Debugging” from the Debug menu. The console application prompts me to press any key, which I do and I wait for the “Disposed all objs, press any key to continue” prompt. At this point I use JustTrace to take a snapshot of the application’s memory. I then press a key to close the console application and turn my attention back to the snapshot that JustTrace took.
Figure 2: JustTrace memory snapshot
From the list of view on the left of the snapshot I am going to select Disposed Objects from the Analyses group. This shows me a list, by type, of all the objects that at the time of the snapshot had had their Dispose method called, but had not been garbage collected:
Figure 3: Disposed Object by Type
In this case I can see that I have five instances of MyDisposableObject remaining in memory for a total of 60 bytes. Again, MyDisposableObject is simply an empty class with an empty Dispose method, so the low memory size is understandable. Most classes you will be working with in a normal application are probably (hopefully!) not going to be empty, so that number could be much larger and have a more significant impact on your application’s resources.
So now that I know I have objects that have implemented IDisposable and have had their Dispose method called but are not being garbage collected I need to find out where they are and what is still holding a reference to these object to keep them around. I can double click on the type name in the Disposed Objects view to see a more detailed view called “Instances of MyDisposableObject" (your object name will replace “MyDisposableObject” when you use this feature). This view gives me a more detailed view of what’s happening with these objects in regard to memory and why they are still hanging around:
Figure 4: Instance of MyDisposableObject
In this view I can see each object’s Object ID, the type name, and sizes of each object. I also have a column called Age. Age represents how many times the object has survived garbage collection. As you can see in even a short and simple application the garbage collector ran twice. In large applications this number could get quite high signifying many missed chances to reclaim resources. I can double click on any of these objects to see a Root Path diagram for that particular instance. Double clicking on the first object in the list (the one with the Object ID of two) results in the root path diagram in figure 5:
Figure 5: The root path diagram for object two
The blue box at the top of the diagram indicates the application root that is ultimately responsible for the object not being garbage collected. In this case you can see that it is the List named _myList that is preventing the instance of MyDisposableObject form being garbage collected by holding a reference to it. Quite simple, if the object was removed from list or the list itself was de-referenced, the object would be garbage collected and it’s memory freed. In this case the instance of MyDisposableObject only has one application root, but it’s possible, and likely that objects will have multiple application roots.
As you have seen, objects that implement the IDisposable interface are capable of easily creating difficult to locate memory leaks in .NET applications. If developers are not careful, especially in larger applications, unknown or unexpected references to these object can keep them around, and keep their memory from being freed. JustTrace will help you not only find occurrences of these objects; it will help you find out why they are still around. This feature is only available in the 2013 Q2 release of JustTrace. So if you don’t already have JustTrace download the trial today and start finding the zombies in your code today!
Copyright © 2017, Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
Progress, Telerik, and certain product names used herein are trademarks or registered trademarks of Progress Software Corporation and/or one of its subsidiaries or affiliates in the U.S. and/or other countries. See Trademarks or appropriate markings.