|
|
|
|
|
|
Memory Leaks in Managed Code
When writing a Window Forms application, it's useful to display the current memory usage in the "About" box. It comes in handy when trouble shooting. It usually proves that memory usage it not the problem, but sometimes it proves the opposite: the application is chewing up more and more RAM.
How can that be? How can a managed application leak memory?
Types of Managed Memory Leaks
There are two main types of "leak" in managed applications
1. Unintended references keeping managed objects alive
You're not using an object any more, so you expect that the garbage collector will clean it up. But the garbage collector doesn't. Why? There is only one possible reason: something, somewhere, still has a reference to the object. Perhaps you put the object in some global (i.e. static) list or perhaps it is referred to be some other object which you are still using.
So this isn't a "real" leak at all, it just looks like one. You've forgotten about a reference to your object, but the garbage collector hasn't. It sees the reference and keeps the object alive.
Of course, a reference only counts if it can be (recursively) traced back to a "root" object which is still in use - i.e. a static field or a local variable/parameter in a currently executing method. References from other objects which are, themselves, due for garbage collection will not keep an object alive. In fact, the garbage collector never even sees them. (It just starts from the roots and recursively visits reachable objects.)
Event handlers are a common cause of this problem. You create objects A and B then run some code like this:
A.SomeEvent += new EventHandler(B.SomeMethod)
Later, you finish using B, but you're still using A. Your on-going use of A keeps B alive, since A refers to B by way of the event handler. If you want B's lifetime to be shorter than A's, you have to unhook the event handler with a line like this:
A.SomeEvent -= new EventHandler(B.SomeMethod) (note the -=)
Of course, event handlers aren't the only cause of this problem, any reference between objects will do. Consequently, these problems can be difficult to track down. You can't think what the remaining reference is, but there must be one there, somewhere. To help track it down try a tool like SciTech .NET Memory Profiler. Or, if you prefer free development tools, try Microsoft's SOS debugger extension. But be warned, it can be a bit complicated.
2. Managed objects holding unmanaged resources
There are two variants of this problem:
(a) Badly-written managed objects, which don't clean-up after themselves. Of course, you don't write any of these, so let's move straight on to (b)…
You don't write bad objects because you know that a good managed object should always implement IDisposable if it uses unmanaged resources. A good managed programmer will use that interface to clean the object up when he or she has finished with it. IF the programmer forgets, the good managed object will clean up the unmanaged resources anyway when the garbage collector collects the object.
(b) Small managed objects, which use significant amounts of unmanaged memory. The .NET 1.1 garbage collector cannot see the unmanaged memory. It just sees the small managed bit, so it thinks there's no need for a collection. (If it did do a collection, the well-written managed object would indeed free the unmanaged memory, but the garbage collector doesn't realise a collection is required.).
Bitmaps are a classic example of this problem, since they have a small managed component and a large unmanaged part. Under .NET 2.0, there is a new method which such objects can call to inform the garbage collector of their true size. It will solve this problem (as long as it's used correctly).
Other Types of Managed Memory Problems
Two other leak-like problems are possible (although relatively unlikely, in practice, I'd say). The first relates to "pinned" objects preventing the garbage collector from moving objects around. This is an internal .NET implementation issue, and has been significantly improved in .NET 2. The second relates to declaring lots of large objects and never letting them leave scope. In the unlikely even that you find yourself doing that - and suffering unacceptable memory usage - you should stop doing it :-)
This page itemizes all the kinds of memory leaks that I've listed here (although it fails to mention managed objects holding unmanaged resources).
What to Do
What NOT to Do
When should you set things to null? I can think of a couple of situations: (1) When a small object with long lifetime refers to a large object with a short lifetime. Say you have two objects, A and B, and A contains a field that refers to B. If B is a very large object and you want its lifetime to be significantly shorter than that of A, then consider setting the field to null when you've finished with B. But don't go overboard - you probably won't need to do this very often. (2) Another situation, relating specifically to servers with threads waiting on external resources,is described here. Make sure you read the comments at the end.
Happy coding!
Page History: Created 6 Feb 06
Several links updated: 12 Feb 06
|
|
|
Copyright (c) 2003-2006, John Rusk.
|
||