Click here to Skip to main content
Email Password   helpLost your password?

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:

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:

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.

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:

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:

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionFound lots of objects. Now what?
Paul Meems
2:13 25 Aug '09  
Thanks for this great article.
I used it on a big application we have and got over 1000 undisposed objects in our custom code and over 800 objects in System. code.
How do I interpreter the log file?
How can I see what object is not disposed, so I can fix it?

Thanks,
Paul
AnswerRe: Found lots of objects. Now what?
S. Senthil Kumar
2:18 25 Aug '09  
For each object, you will now know the type of the object and the full stack trace of its constructor (click on the object type row in the log viewer). Armed with this information, you can hopefully look at the source code and figure out why it's not being disposed.

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

Generalvery good - my vote of 4!
Pratik.Patel
18:42 23 Aug '09  
This is very good. I am not big fan of C++ but I would definitely love to run your program to see if I can see undisposed objects of my application or not. Thanks for posting this article.

Regards,
Pratik

People say dont re-invent the wheel, I say I've got a better wheel!

Generalhow to test it?
elvis_pan
23:59 21 Aug '09  
i write the follow code for test finding undisposed object.build and link it to exe file.

static void Main(string[] args)
{
FileStream fs = new FileStream("D:\\videotest\\mtv.wmv", FileMode.Open);
byte[] bs = new byte[100];
fs.Read(bs, 0, 100);

Bitmap b = new Bitmap("D:\\picfortest\\a.bmp");
System.Data.SqlClient.SqlCommand sqlc = new SqlCommand();


}

Using the UndisposedViewer.exe say undisposed == 0. "You are responsible for unmanaged resources such as file handles, database connections, GDI+ objects, COM objects, and other system objects."(Effecitve C# Chapter 2. .NET Resource Management)

where is the error?
I use the UndisposedViewer.exe in the wrong way?
Thanks
(btw:i have register the dll,and it would find the window app's deviceContext undisposed.)
GeneralRe: how to test it?
S. Senthil Kumar
2:13 22 Aug '09  
elvis_pan wrote:
where is the error?
I use the UndisposedViewer.exe in the wrong way?


Try adding a GC.Collect before you return from Main. The application relies on FinalizeableObjectQueued notifications from the profiling API - in your case, the program starts and terminates so rapidly that I guess GC/finalization queuing did not happen.

Calling GC.Collect explicitly should show the leaking objects. The latest version on codeplex http://codeplex.com/undisposed[^] has a setting to periodically trigger GC automatically, but I doubt even that would help in your case. In the next version, I'm planning to hook Main and insert a GC.Collect automatically at the end during execution.

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

GeneralCLR Profiler
zubairy
19:20 21 Aug '09  
we already have CLR profiler for dotnet applications, can you tell me what is the main difference between Microsoft CLR profiler and this application.

Good Luck

GeneralRe: CLR Profiler
S. Senthil Kumar
8:28 22 Aug '09  
zubairy wrote:
can you tell me what is the main difference between Microsoft CLR profiler and this application.


CLR Profiler lets you look at objects on the heap and track allocations, but AFAIK, it doesn't show you finalized objects (and their stack traces).

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

QuestionStill cannot find log file
hat_master
11:53 18 Aug '09  
I've tried both the Code Project file and the Codeplex file and I still receive an error that the log file cannot be found. Thoughts?

Thanks
AnswerRe: Still cannot find log file
S. Senthil Kumar
18:25 18 Aug '09  
Can you check if a log file is actually generated in the location you specified? If there isn't one, did you register Undisposed.dll using regsvr32 Undisposed.dll?

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

GeneralWhat is Leaky.exe?
BOWLINGBALL
3:14 18 Aug '09  
Solution also has Leaky.exe - what does it do?
GeneralRe: What is Leaky.exe?
S. Senthil Kumar
3:38 18 Aug '09  
It is a test project that creates managed objects and doesn't dispose all of them i.e. leaks some of them.

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

QuestionError: Could not find file : and
liquidfirexyz
9:56 12 Aug '09  
I launch the program and press go, then as soon as I close the application I was watching I get the error :

"Could not find file : and "

Any ideas?
AnswerRe: Error: Could not find file : and
S. Senthil Kumar
22:01 12 Aug '09  
This is a bug - the application isn't handling log file paths with space properly.

I've fixed it though, and the updated version should be available in CodeProject shortly. If you're in a hurry, you can download the updated version from http://undisposed.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=31494[^]

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

GeneralInteresting, but it doesn't work
jirka.stejskal@gmail.com
2:13 12 Aug '09  
I'm working in mixed environment and the amin app is pure and simple C++. .NET is used for UI stuff, XML handling and some database access layers. When I hit "Automatically find ...", I got a crash (UE).
GeneralRe: Interesting, but it doesn't work
S. Senthil Kumar
2:26 12 Aug '09  
jirka.stejskal@gmail.com wrote:
d ...", I got a crash (UE).


I haven't tried the automatic type finder on mixed mode assemblies. I'll check that out, thanks.

Does it work if you give it fully qualified type names instead?

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

GeneralRe: Interesting, but it doesn't work
jirka.stejskal@gmail.com
3:55 12 Aug '09  
I tried to give it one type. It launched program - very slowly (side note - our program is BIG and there is a lot of COM). It usually loads in about 30s, now it took about 3-4 minutes. When I finished it, it reported "Couldn't find ...". Lukks like you do not suport space inside the Log file path.
GeneralRe: Interesting, but it doesn't work
S. Senthil Kumar
22:08 12 Aug '09  
jirka.stejskal@gmail.com wrote:
It launched program - very slowly

I'm afraid you're not going to get the same performance as a normal run. You are effectively running your application through a profiler, so it is bound to run slower. There are a few places I can optimize though, like doing IO (writing to log) on a different thread, but they'll have to wait for the next version.

jirka.stejskal@gmail.com wrote:
When I finished it, it reported "Couldn't find ...". Lukks like you do not suport space inside the Log file path.


You got that right, the code wasn't handling log file paths with spaces correctly.

I've fixed it though, and the updated version should be available in CodeProject shortly. You can also get it right away from http://undisposed.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=31494[^].

Fully qualified type name simply means the type name prefixed by the namespace it resides in (System.IO.FileStream, for e.g.)

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

GeneralRe: Interesting, but it doesn't work
jirka.stejskal@gmail.com
3:57 12 Aug '09  
One more note - I tried lo locate Log file, but it wasn't created. Not sure if I used right fully qualified name. It is the one alppearing on the class browser at th etop of the source (C#) page, right?
Generaloh my god !!!!!!!!!!!1
zzx_123456
21:26 9 Aug '09  
Sniff Sniff Thumbs Upyour artices is very nice .i like it.it is heip me to sove the wepens.thank you .
GeneralA couple of bugs
pfulghum
16:09 9 Aug '09  
I think there are a couple of bugs in here.
During a GC...the profiler can call "RootReferences, SUrvivingReference, and MovedReference several times.

I don't think your code handles that case...

Try testing it with 500,000 live objects that come and go...

-- Pat
GeneralRe: A couple of bugs
S. Senthil Kumar
17:49 10 Aug '09  
pfulghum wrote:
During a GC...the profiler can call "RootReferences, SUrvivingReference, and MovedReference several times.

I'll have to check this out, thanks.


pfulghum wrote:
Try testing it with 500,000 live objects that come and go...


What happened when you created that many objects? Crash? Hang? Wrong results?

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

GeneralMicrosoft Security Bulletin MS09-035
hhmiles2
12:25 4 Aug '09  
Has this code been recompiled to address the following security bulletin? Since it requires the C++ redistributabe package
http://www.microsoft.com/technet/security/bulletin/ms09-035.mspx[^]

I belive that this utility can be very helpful. I don't want to see the authors hard work impeeded by security vulnerabilites.
GeneralGood work
Shivprasad koirala
20:20 2 Aug '09  
Nice work keep it up.

Visit my 500 videos on WCF,WPF,WWF,Silverlight,UML design patters @ http://www.questpond.com

GeneralA lot of undisposed object found. [modified]
ddecoy
6:01 30 Jul '09  
I tested this on some applications that I wrote and to my surprise found a lot of undisposed object.
After explicitly disposing everything that could be disposed the n° of undisposed object remained.
The most undisposed object were from type: System.Windows.Forms.Internal.DeviceContext (88 objects!) Sigh

Someone can clearify how to interpret the amount of undisposed objects and how to fix this ?

modified on Friday, July 31, 2009 3:10 AM

GeneralRe: A lot of undisposed object found.
S. Senthil Kumar
20:40 2 Aug '09  
ddecoy wrote:
The most undisposed object were from type: System.Windows.Forms.Internal.DeviceContext (88 objects!) Sigh


I get those too. The constructor stack trace tells that they are created deep within the Windows Forms library code, so there's not much you can do.

You could always ask Undisposed to ignore instances of type System.Windows.Forms.Internal.DeviceContext by removing that line from the "Types to monitor" textbox. I guess I should add a "Ignore System Types" checkbox in the next version.

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


Last Updated 13 Aug 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010