BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

.NET Framework
.NET 1.1+

.NET Garbage Collection

Developers of managed software using the .NET framework do not need to deallocate the memory used by objects when they go out of scope, reducing the risk of creating bugs that cause memory leaks. Release of memory is controlled by the garbage collector.

Generations

The simplified description above describes a full garbage collection. As this is an expensive process, there are optimisations that improve performance. One of these is the concept of generations of objects within the managed heap.

All newly created objects are considered as generation zero. When the garbage collector runs, any objects that survive are advanced to generation one. Generation one objects that survive another garbage collection become generation two, which is the highest generation number. Due to the contiguous nature of the heap, and the manner in which it is defragmented, objects of the same generation are always contiguous too.

The garbage collector makes assumptions about objects according to their generation number and optimises collection accordingly. Often this means a partial garbage collection that only focuses on generation zero objects, or a slightly larger process that encompasses both generation zero and generation one objects. The generations are considered as follows:

  • Generation zero objects are those that have recently been created. These are likely to include temporary variables that will go out of scope quickly. This assumption allows for a partial garbage collection that focuses on generation zero objects, as they have the greatest potential to be unreachable. Often only generation zero objects need to be deallocated to free up enough memory for a new object.
  • Generation one objects have survived a garbage collection. These objects are more likely to be long-lived than those in generation zero, so are less likely to be dead and thus able to be released. For this reason, the garbage collector is less likely to consider items of this generation.
  • Generation two objects have survived two or more garbage collection operations. These objects are most likely to be usually stable and long-lived. Some might exist for the entire time that the application is running. Accordingly, the garbage collector will spend the lowest amount of time trying to release memory for these items. This improves the overall performance of the garbage collector but means that dead, generation two objects can take up memory unnecessarily for very long periods.

Managed and Unmanaged Resources

The garbage collector only knows how to release memory for managed objects. This presents a problem when working with unmanaged resources, such as files or database connections. If the garbage collector cleans up a dead object that used such resources, the resources could be orphaned and remain allocated, despite being unreachable. This is the reason for implementing the IDisposable pattern. When an object that implements the IDisposable interface is disposed, either manually or automatically, the unmanaged resources that it uses can be cleanly released.

A second common part of the IDisposable pattern is the inclusion of a finalizer that calls Dispose. Ideally you should call Dispose yourself when you know that an object that uses the pattern is no longer required. If you do not, the garbage collector will use the finalizer, which must call Dispose to release unmanaged resources. The Dispose method should also include a call to the SuppressFinalize method of the GC class:

GC.SuppressFinalize();

The above call ensures that the garbage collector will not call the finalizer method when deallocating the memory for an object. This is important due to a subtlety in the garbage collection process when applied to objects with finalizers. If such an object is found by the garbage collector and the finalizer has not been suppressed, the garbage collection will not release it immediately. Instead, it will be set to be processed by the finalization thread.

The finalization thread executes the finalizers on all dead objects that have them. As well as creating a performance impact, allocation to the finalization thread means objects can't be immediately collected. Instead they are moved to the next generation to be cleaned up in a subsequent garbage collection run, after finalization. Any objects that the dead object references are also prevented from being released, decreasing the amount of the heap that can be reclaimed.

Guidelines

Garbage collection makes it easy and safe to work with .NET managed objects without having to worry about ensuring that they are correctly released. However, as you can see, the way in which you create objects and allow them to be automatically released has some bearing on the amount of work that the garbage collector has to do, and the level of impact this can have on your application's performance. To minimise this impact you should try to consider a few guidelines:

  • Try not to create objects unnecessary. This includes when manually creating objects with the new keyword and means considering alternatives when using standard .NET framework methods. For example, if you need to extract only the first item from a comma-separated list held in a string, you could look for the first comma and use Substring to extract the information you need. This would create one additional string. You could also split the string using the Split method, then retrieve the first item from the resultant array. However, this could cause the creation of many unused string objects.
  • If you know an object will be long-lived, so it is likely to reach generation two, you should try to keep it as small as possible. These objects can take up memory for a long time, even after they are unreachable, and are of the lowest priority to the garbage collector. Minimising the amount of memory they use can lower the number of garbage collection processes that will be required.
  • Ensure that objects that implement IDisposable can be correctly disposed by the garbage collector and that unmanaged resources are always reclaimed correctly. You should also include a call to GC.SuppressFinalizer in disposable classes that include a finalizer. This reduces the chance that the objects will reach the finalization thread and be prevented from being released during garbage collection. Call Dispose on such objects when they are no longer required.
  • For classes that do not require the IDisposable pattern, avoid the use of finalizers altogether wherever possible. This prevents such objects from being passed to the finalization thread, permitting them to be released without promoted to higher generations.
  • Try to avoid creating complex classes that generate large, interconnected object graphs. Those object graphs can hold many references, sometimes unnecessarily, which can prevent the garbage collection releasing objects.
6 November 2012