One of my first postings was this one: Two things to avoid for better memory usage in which I gave some approximately correct advice (is there any other kind? <g>) about using GC.Collect(). I still stand by this advice but I think maybe this is a good time to expand on it. So now I offer you Rico’s rules for calling GC.Collect()
This is really the most important rule. It’s fair to say that most usages of GC.Collect() are a bad idea and I went into that in some detail in the orginal posting so I won’t repeat all that here. So let’s move on to…
Consider calling GC.Collect() if some non-recurring event has just happened and this event is highly likely to have caused a lot of old objects to die.
A classic example of this is if you’re writing a client application and you display a very large and complicated form that has a lot of data associated with it. Your user has just interacted with this form potentially creating some large objects… things like XML documents, or a large DataSet or two. When the form closes these objects are dead and so GC.Collect() will reclaim the memory associated with them.
Now why would I suggest this as a possible time to call the collector? I mean, my usual advice goes something like “the collector is self-tuning so don’t mess with it.” Why the change of attitude you might ask?
Well here is a situation where the collector’s tendancy to try to predict the future based on the past is likely to be unsuccessful. In a well behaved client application, even while the form is running almost all the objects will die in generation 0 or generation 1. Only the initial objects associated with the form will go into generation 2. Any objects that do find their way into generation 2 are likely to stay there for the duration of the form. Now this is a great situation from a performance perspective. The Gen0 and Gen1 collects are nice and cheap. Promotion to Generation 2 is nice and low. The collector is thinking to itself “Ah, life is good. There’s no trash in gen2 and everything is dying in gen1 and gen0. No need for any nasty gen2 collections.” And sure enough the collector is dead right. There is no need for a gen2 collect. Your dialog will get great gc performance. Everything is wonderful.
Right up until that close button is pressed.
When the form is (finally) closed all those objects related to the form that were in generation 2 just died. But if your application is well behaved even after the dialog is closed everything is still tending to die in gen0 and gen1. It all looks the same to the collector. It doesn’t know a dialog just closed and lots of long lived objects are now dead. With nothing new going into generation 2 it still looks like a collection would be a waste of time. But it isn’t.
The way this looks to you observing the client application is that when the form is closed hardly any memory is returned to the system. If you look at the GC counters you’ll see a big GC heap.
Contrast this with say a web server that is getting regular requests. When a web form is finished, sure there’s some junk in gen2 (hopefully not too much or you’ll get mid-life crisis) but another form is coming along in just a few milliseconds and so there will be objects going into generation 2 for that form at the usual rate. All of this is nice and predictable and the collector will soon get into a nice groove. It’s the repitition that’s making everything wonderful in the server case.
So, when a non-repeating event involving a lot of object deaths occurs (such as the completion of startup-time work, or the closing of a big dialog), consider using GC.Collect() at that time to reclaim long lived objects.
Don’t bother doing this if there aren’t a lot of old objects involved.
Rule #1 should trump Rule #2 without strong evidence.