Is correct to use GC.Collect(); GC.WaitForPendingFinalizers();?

The short answer is: take it out. That code will almost never improve performance, or long-term memory use.

All your points are true. (It can generate a deadlock; that does not mean it always will.) Calling GC.Collect() will collect the memory of all GC generations. This does two things.

  • It collects across all generations every time – instead of what the GC will do by default, which is to only collect a generation when it is full. Typical use will see Gen0 collecting (roughly) ten times as often than Gen1, which in turn collects (roughly) ten times as often as Gen2. This code will collect all generations every time. Gen0 collection is typically sub-100ms; Gen2 can be much longer.
  • It promotes non-collectable objects to the next generation. That is, every time you force a collection and you still have a reference to some object, that object will be promoted to the subsequent generation. Typically this will happen relatively rarely, but code such as the below will force this far more often:

    void SomeMethod()
    { 
     object o1 = new Object();
     object o2 = new Object();
    
     o1.ToString();
     GC.Collect(); // this forces o2 into Gen1, because it's still referenced
     o2.ToString();
    }
    

Without a GC.Collect(), both of these items will be collected at the next opportunity. With the collection as writte, o2 will end up in Gen1 – which means an automated Gen0 collection won’t release that memory.

It’s also worth noting an even bigger horror: in DEBUG mode, the GC functions differently and won’t reclaim any variable that is still in scope (even if it’s not used later in the current method). So in DEBUG mode, the code above wouldn’t even collect o1 when calling GC.Collect, and so both o1 and o2 will be promoted. This could lead to some very erratic and unexpected memory usage when debugging code. (Articles such as this highlight this behaviour.)

EDIT: Having just tested this behaviour, some real irony: if you have a method something like this:

void CleanUp(Thing someObject)
{
    someObject.TidyUp();
    someObject = null;
    GC.Collect();
    GC.WaitForPendingFinalizers(); 
}

… then it will explicitly NOT release the memory of someObject, even in RELEASE mode: it’ll promote it into the next GC generation.

Leave a Comment