I’ve started to review some code in a project and found something like this:
GC.Collect();
GC.WaitForPendingFinalizers();
Those lines usually appear on methods that are conceived to destruct the object under the rationale of increase efficiency. I’ve made this remarks:
- To call garbage collection explicitly on the destruction of every object decreases performance because doing so does not take into account if it is absolutely necessary for CLR performance.
- Calling those instructions in that order causes every object to be destroyed only if other objects are being finalized. Therefore, an object that could be destroyed independently has to wait for another object’s destruction without a real necessity.
- It can generate a deadlock (see: this question)
Are 1, 2 and 3 true? Can you give some reference supporting your answers?
Although I’m almost sure about my remarks, I need to be clear in my arguments in order to explain to my team why is this a problem. That’s the reason I’m asking for confirmation and reference.
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 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:
Without a
GC.Collect(), both of these items will be collected at the next opportunity. With the collection as writte,o2will 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
o1when callingGC.Collect, and so botho1ando2will 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:
… then it will explicitly NOT release the memory of someObject, even in RELEASE mode: it’ll promote it into the next GC generation.