Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / Windows Forms

Finding Undisposed Objects

Rate me:
Please Sign up or sign in to vote.
4.93/5 (105 votes)
13 Aug 2009Ms-PL5 min read 191.1K   2.9K   235   61
An application to find undisposed objects in your .NET application.

Introduction

Who likes to see an unkempt room? A street littered with garbage? A smelly toilet?

Who likes to see objects go undisposed in managed code?

Hopefully, no one.

Non-deterministic garbage collection in .NET makes it your responsibility to dispose objects, and bad things can happen if you don't do that. Besides the cost of finalization, your application could run out of resources and crash if it happens to consume them in a way that doesn't trigger garbage collection.

But, this article isn't about why you need to call Dispose - it assumes you've already seen The Light. This article is about ways to find objects that did not get disposed.

Techniques

The simplest way to find undisposed objects is to add some logging to the finalizer of each disposable type. Running the application through typical scenarios and checking the logs should tell you which types are not being disposed. If you know how to use Windbg and SOS, you can also use the !finalizequeue extension command to view all finalizable types. If the list of finalizable types is small, you can go through your code, looking for missing Dispose calls on instances of those types.

Neither approach works well when there is a huge code base, or if there are third party libraries involved. Enter Undisposed, the subject of this article. It's an application I wrote that uses the CLR profiling API to monitor finalizations and object creations at runtime, and shows you all finalized objects, with stack traces for the respective constructors to help you quickly pinpoint the offending section of code. Here's how the result of the analysis looks:

Image 1

The top panel shows you the number of finalized objects by type, and the bottom panel shows unique stack traces of constructors for the selected type.

How To Use It

Download the application and register Undisposed.dll using:

regsvr32 Undisposed.dll

The DLL requires VC redistributable for VS 2008 SP1, so if registration fails, please install the redistributable from here.

Launch UndisposedViewer.exe:

Image 2

Enter the path of the application to launch, command line arguments (if any), and a log file location. Click on "Automatically find finalizable types" to search for all finalizable types in the application. You can also enter the type names manually. The list of types in the textbox at the bottom will be the ones that will be monitored for finalization. Hitting Go will launch the target application. Put the application through its paces, and once you exit the application, Undisposed LogViewer will launch automatically, showing you the undisposed objects in that specific run of the application.

How It Works

Undisposed.dll

The key component in this application is Undisposed.dll - a COM component that hooks up with the CLR profiling API. In a throwback to old times, the CLR relies on plain old environment variables to know what profiler to load. The "COR_ENABLE_PROFILING" environment variable tells the CLR that profiling is enabled, and it reads the "COR_PROFILER" environment variable to get the CLSID of the component to be loaded as the profiler.

The CLR Profiling API exposes a ICorProfilerCallback2 interface which must be implemented by the COM component. Once loaded, the CLR calls back into the component using methods on that interface. There are a lot of methods there, but these are the ones Undisposed uses.

  • Initialize - Initializes all trackers
  • Shutdown - Shuts down all trackers
  • FinalizeableObjectQueued - Notifies the finalization tracker, which writes out the details to log
  • ObjectAllocated - Notifies the object tracker, which maps objects with the stack trace obtained by walking the stack
  • SurvivingReferences - Notifies the object tracker about objects that survived a garbage collection
  • MovedReferences - Notifies the object tracker about objects that were moved during a garbage collection

The Initialize callback is also used to get an interface pointer to ICorProfilerInfo. The SetEventMask method on that interface is used to tell the CLR the events that the profiler is interested in. For Undisposed, they are:

C++
hr = m_pCorProfilerInfo->SetEventMask(COR_PRF_MONITOR_CLASS_LOADS 
        | COR_PRF_MONITOR_OBJECT_ALLOCATED 
        | COR_PRF_MONITOR_GC 
        | COR_PRF_ENABLE_OBJECT_ALLOCATED
        | COR_PRF_ENABLE_STACK_SNAPSHOT
        )

The COR_PRF_ENABLE_STACK_SNAPSHOT lets the profiler walk the stack using ICorProfilerInfo2::DoStackSnapshot. This is used to get the constructor stack trace for the object the profiler is tracking.

There are lots of other interesting implementation details, enough to warrant a separate article. At a high level though, this is how the code is organized:

Image 3

The Controller forwards calls from the CLR to the appropriate tracker objects. ObjectTracker maintains the list of live objects of all monitored types, along with the stack trace of their constructors. It also does the book keeping after a garbage collection is done. FinalizerRunLogger writes out the object type and the stack trace when it gets notified that an object of one of the monitored types is about to be finalized. TypeTracker, not shown above, maintains a map from the internal data structures to the type names.

UndisposedViewer.exe

The main application merely launches the target application after setting up the environment variables to load Undisposed.dll. It also writes its current state to a config file that is then read by the COM component. This is needed to pass things like the list of types to monitor.

UndisposedLogViewer.exe

The log viewer is launched after the target application exits. There is not much logic in there, it simply reads the log file generated by Undisposed.dll and shows the data grouped by type and stack trace.

Conclusion

The inspiration for writing this application came from this post in the Lounge. Hopefully, it will prove useful to you as well. Feedback and suggestions are welcome.

History

  • 20-07-2009 - Initial version
  • 13-08-2009 - Updated source code and binaries

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer Atmel R&D India Pvt. Ltd.
India India
I'm a 27 yrs old developer working with Atmel R&D India Pvt. Ltd., Chennai. I'm currently working in C# and C++, but I've done some Java programming as well. I was a Microsoft MVP in Visual C# from 2007 to 2009.

You can read My Blog here. I've also done some open source software - please visit my website to know more.

Comments and Discussions

 
GeneralNice, but a question Pin
Dewey20-Jul-09 21:29
Dewey20-Jul-09 21:29 
GeneralRe: Nice, but a question Pin
S. Senthil Kumar20-Jul-09 21:43
S. Senthil Kumar20-Jul-09 21:43 
GeneralRe: Nice, but a question Pin
Dewey21-Jul-09 10:29
Dewey21-Jul-09 10:29 
GeneralRe: Nice, but a question Pin
ThatsAlok23-Jul-09 6:02
ThatsAlok23-Jul-09 6:02 
GeneralGreat. Pin
Luc Pattyn20-Jul-09 14:34
sitebuilderLuc Pattyn20-Jul-09 14:34 
GeneralExcelent Pin
Abhijit Jana20-Jul-09 12:24
professionalAbhijit Jana20-Jul-09 12:24 
QuestionCan this catch leaky iDisposable objects without finalizers? Pin
supercat920-Jul-09 11:18
supercat920-Jul-09 11:18 
AnswerRe: Can this catch leaky iDisposable objects without finalizers? Pin
S. Senthil Kumar20-Jul-09 18:44
S. Senthil Kumar20-Jul-09 18:44 
supercat9 wrote:
but objects which handle events may need to implement iDisposable even though they have no unmanaged resources and thus need no finalizer


True. This tool won't catch those, because it is looking for finalizer calls, not Dispose method calls. The title of the article is a bit misleading in that sense, because having a Dispose doesn't necessarily require having a finalizer (although the reverse is true), but that's the most common case, IMO. I guess I should update the article to make that clear.

supercat9 wrote:
Should such objects register a do-nothing finalizer when created, and unregister it when they're disposed, or what would be the best method of catching them?


That would work, but just having a finalizer on a type causes a perf hit, because the object is added to a separate queue to maintain a strong reference after all other references are lost (for calling the finalizer). If the perf hit matters to you, conditional compilation (writing the finalizer inside #ifdef SYMBOL) would help avoid taking the hit in release builds, yet find the issue in test builds.

A good memory profiler, or even Windbg with SOS, would be a better solution, IMO. Unlike the unmanaged resource case, your objects still live on the heap, and a snapshot of the heap when you're close to running out of memory should point you to the problem.

Regards
Senthil
_____________________________
My Home Page |My Blog | My Articles | My Flickr | WinMacro

GeneralVoted 5 Pin
Nish Nishant20-Jul-09 7:09
sitebuilderNish Nishant20-Jul-09 7:09 
GeneralVery Nice Pin
Rama Krishna Vavilala20-Jul-09 7:02
Rama Krishna Vavilala20-Jul-09 7:02 
GeneralRe: Very Nice Pin
Nish Nishant20-Jul-09 7:07
sitebuilderNish Nishant20-Jul-09 7:07 
GeneralRe: Very Nice Pin
VMykyt22-Jul-09 23:10
VMykyt22-Jul-09 23:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.