Most C# programmers know that memory leaks are a very real problem in the managed environment. It may sound counter-intuitive to beginning programmers, however, memory does leak in the .NET world. Finalize, Dispose and the garbage collecter do make our lives easier, however they are not a catch all mechanism. As is well known, overriding finalize in an object results in a pointer to that object getting created on the finalization queue. Further it takes 2 garbage collections to actually finalize it(one to move the the object from the heap to the finalization reachable queue and another to invoke the thread that will call the finalize method). And there is only 1 thread which calls all the finalization methods in a appdomain. The finalize calls do not occur in any predefined sequence. That is the reason why one should be careful about what code to put in the finalize method.
Now, the way the garbage collector is structured, it is supposed to find out what objects in memory are reachable. An object is reachable if another root object has a reference to it(it is a variable on the stack, it is a static variable or is on the finalization queue, or object pointers stored in the cpu registers). It simply removes the non-reachable items.
GC can become expensive because the objects that survive every generation are compacted and moved to the end of the heap. A garbage collection causes all threads that are making .NET calls to be suspended until the heap is cleaned and compacted.
Seems like a fool-proof plan: the GC will figure out what objects are old and get rid of them. No need to de-allocate memory. Unlike C++, we do not need to call destructor on every object. As a matter of fact, a C# destructor does an implicit finalize() call. So if one uses an empty destructor, that particular object becomes 1 generation older in the GC phase before it is removed. This causes a needless loss of performance because the GC works on the principle that younger objects[Gen1] go out of reference faster then older objects[Gen1 or Gen2].
However, there is one catch: all references to that object need to be removed before the object is removed. So if one "deletes" an object but does not remove all references to it, it will never get collected and that memory will never be freed by the garbage collector.