The golden rule of .NET memory management is simple:

Allocate a class instance onto the managed heap using the new keyword and forget about it.

The garbage collector destroys an object when it is no longer needed, which is (basically) when the object is unreachable by the code base.

The next rule of garbage collection is also simple:

If the managed heap does not have sufficient memory to allocate a requested object, a garbage collection will occur.

An application root (or, simply root) is a storage location containing a reference to an object on the heap. It can be any of these:

  • A reference to a global object (C# does not permit these, but CIL does)
  • A reference to any static object or static field
  • A reference to a local object within an application’s code base
  • A reference to object parameters passed into a method
  • A reference to an object waiting to be finalized
  • Any cpu register that references an object

During garbage collection, the CLR builds an object graph representing each reachable object on the heap. Objects on the heap that are not on the object graph are marked for termination and then swept from memory. Then the remaining space on the heap is compacted, and active roots and the next object pointer are updated.

Not all objects are examined, however. Each object belongs to one of three generations:

  • Generation 0: A new object that has never been marked for garbage collection.
  • Generation 1: An object that has survived garbage collection once (it was marked for collection but not removed because sufficient space was acquired).
  • Generation 2: An object that has survived garbage collection twice or more.

Generation 0 and 1 objects are called ephemeral generations.

During garbage collection, generation 0 objects are evaluated first. Any objects that survive are promoted to generation 1. If all have been evaluated but additional memory is required, generation 1 objects are investigated. Any generation 1 objects that survive are promoted to generation 2. If additional memory is still required then generation 2 objects are investigated. This process helps improve the performance of garbage collection by checking objects that are more likely to be unreachable first.

If necessary, garbage collection can be forced manually using System.GC.Collect(). Collect can take two arguments:

  • The first argument represents the oldest generation to perform garbage collection; i.e., Collect(0) will only investigate generation 0 objects.
  • The second argument can be either GCCollectionMode.Forced or GCCollectionMode.Optimized (or Default, which will use Forced). Forced tells the runtime to collect immediately, while Optimized allows the runtime to determine whether to run now or wait.

When doing this, you should make a call to GC.WaitForPendingFinalizers() to suspend the thread until finalizable objects have had a chance to perform cleanup.

A finalize method should be provided for a class that needs to perform cleanup on unmanaged resources prior to being garbage collected. This is done by prefixing the tilde symbol to the class name, with no return type, access modifier, or parameters.

If a class uses unmanaged resources, it may also implement the IDisposable interface (just one method: Dispose()) after which the object user can manually call Dispose when they are finished using the object. Structs can also implement this, whereas they cannot create finalize methods. Naturally, this means any class that implements IDisposable should be Dispose()d when you are finished using it. Note that some such classes provide an alias to Dispose, such as System.IO.FileStream.Close().

An object that implements IDisposable can be used in a special way that automatically calls Dispose() when it is finished, with the using keyword:
using(MyDisposableClass mdc = new MyDisposableClass()) {
mdc.DoSomething();
}

using(MyDisposableClass mdc2 = new MyDisposableClass(),
mdc3 = new MyDisposableClass()) {
mdc2.DoSomething(mdc3);
}

With this syntax, each instance will automatically call Dispose() after the end of the using block. Note that the objects must be of the same type if trying to “use” multiple disposable objects.

The best approach is to combine these techniques, to guarantee that unmanaged objects will be cleaned up while still offering the ability for the user object to free them at an earlier time.

Categories: .NETC#Uncategorized

Leave a Reply