The whole purpose of a Garbage Collection
Once a class is defined, you can allocate any number of objects using the C# new keyword. Understand, however, that the new keyword returns a reference to the object on the heap, not the actual object itself. This reference variable is stored on the stack for further use in your application. When you wish to invoke members on the object, apply the C# dot operator to the stored reference.
When you are building your C# application, you are correct to assume that the managed heap will take care of itself without direct intervention. In fact, the golden rule of .NET memory management is simple:
Allocate an object onto the managed heap using the new keyword and forget about it
Once instantiated, the garbage collector will destroy the object when it is no longer needed. The next obvious question, of course, is “How does the garbage collector determine when an object is no longer needed?”
The answer is that the garbage collector removes an object from the heap when it is “unreachable” by any part of your code base.
Assume you have a method in your Program class that allocate a local IceCream object:
Notice that the IceCream reference (iceCream) has been created directly within the “MakeAnIceCream” method and has not been passed outside of the defining scope. Thus, once this method call completes, the iceCream reference is no longer reachable, and the associated IceCream object is now a candidate for garbage collection. Understand, however, that you cannot guarantee that this object will be reclaimed from the memory immediately after MakeAnIcreCream() has completed. All you can assume at this point is that when the CLR performs the next garbage collection, the iceCream object could be safely destroyed.
The base class libraries provide a class type named System.GC that allows you to programmatically interact with the garbage collector using a set of static members. Typically speaking, the only time you will make use of this type directly is when you are creating types that makes use of unmanaged resources.
The whole purpose of the .NET Garbage collector is to manage memory on your behalf. However, under some very rare circumstances, it may be beneficial to programmatically force a garbage collection using GC.Collect(), specifically:
-
Your application is about to enter into a block of code that you do not wish to be interrupted by a possible garbage collection.
-
Your application has just finished allocating an extremely large numbers of objects and you wish to remove as much of the acquired memory as possible.
If this is the case it may be beneficial to have the garbage collector check for unreachable objects, you could explicitly trigger a garbage collection as follows:
When you manually force a garbage collection, you should always make a call to GC.WaitForPendingFinalizers(). With this approach, you can rest assured that all finalizable objects have had a chance to perform any necessary cleanup before your program continues forward. Under the hood, GC.WaitForPendingFinalizers() will suspend the calling thread during the collection process. This is a good thing as it ensures your code does not invoke methods on an object currently being destroyed.
The GC.Collect() method can also be supplied a numerical value that identifies the oldest generation on which a garbage collection will be performed. For example, you wished to instruct the CLR to only investigate generation 0 objects, you should write the following:
As well, as of .NET 3.5, the Collect() method can also be passed in a value of the GCCollectionMode enumeration as a second parameter, to fine-up exactly how the runtime should force the garbage collection. This enum defines the following values:
Now, back to the topic of how the garbage collector determines when an object is “no longer needed”. To understand the details, you need to be aware of the notion of application roots. Simply put, a root is a storage location containing a reference to an object on the heap. Stricly speaking, a root can fall into any of the following categories:
- References to global objects
- Reference to any static objects / static fields
- Reference to local objects within an application’s code base
- Reference to object parameters passed into a method
- References to objects waiting to be finalized
- Any CPU register that references an object
During a garbage collection process, the runtime will investigate objects on the managed heap to determine whether they are still reachable (aka rooted) by the application. To do so, the CLR will build an object graph, which represent each reachable object on the heap. Object graphs are used to document all reachable objects. As well, the garbage collector will never graph the same object twice.
Assume the managed heap contains a set of objects named A,B,C,D,E,F and G. During a garbage collection, these objects are examined for active roots. Once the graph has been constructed, unreachable objects are marked as garbage.
Once an object has been marked for the termination (C and F in this case) they are swept from memory. At this point, the remaining space on the heap is compacted, which in turn will cause the CLR to modify the set of active applications roots (and the underlying pointers) to refer to the correct memory location (this is done automatically and transparently). Last but not least, the next object pointer is readjusted to point to the next available slot.