Managed Memory Model in the .NET Framework

  • 2/18/2009

Finalization

Finalization occurs during garbage collection. The finalizer is invoked during finalization to clean up resources related to the object. Non-deterministic garbage collection is performed on a generation or Large Object Heap when the related threshold is exceeded. Because of this, there may be some latency between when an object becomes unreachable and when the Finalize method is called. This may cause some resource contention. For example, the action of closing a file in the Finalize method may not occur immediately. This may cause resource contention because, although the file is not being used, it remains unavailable for a period of time.

Non-Deterministic Garbage Collection

Place cleanup code for the non-deterministic garbage collection in the Finalize method, which is implicitly called in the class destructor. The class destructor cannot be called on demand. As mentioned, there may be some latency in the Finalize method running. The class destructor is the method of the same name of class with a tilde (~) prefix.

class XClass {
    // destructor
    ~XClass() {
        // cleanup code
    }
}

For certain types of resources, non-deterministic garbage collection is inappropriate. You should not release resources that require immediate cleanup. Also, managed objects should not be cleaned up in a Finalize method. Order of finalization is not guaranteed. Therefore, you cannot assume that any other managed object has not been already finalized. If that has occurred, referring to that object could raise an exception.

Non-deterministic garbage collection is neither simple nor inexpensive. The lifetime of objects without a Finalize method is simpler. For these reasons, the Finalize method should be avoided unless necessary. Even an empty destructor (which calls the Finalize method), harmless in C++, enlists the object for the complete non-deterministic ride—a very expensive ride. For the purposes of this chapter, objects with destructors are called finalizable objects.

The additional cost of having a Finalize method begins at startup. At startup, objects with a Finalize method have a reference placed on the Finalization queue. This means, when the object is otherwise unreachable, there is an outstanding reference being held on the queue, which will prevent immediate garbage collection.

When the finalizable object is no longer reachable and there is a garbage collection event, the object is not removed from memory. At this time, a normal object that is unreachable would be removed from memory. However, the finalizable object is moved from the Finalization to FReachable queue. This keeps the finalizable object in memory. The current garbage collection cycle then ends.

The FReachable queue holds finalizable objects that are waiting for their Finalize methods to be called. Finalizer thread is a dedicated thread that services the FReachable queue. It calls the Finalize method on the finalizable objects. After the Finalize method is called, the reference to the finalizable object is removed from the FReachable queue. At that time, there are no outstanding references to the finalizable object.

During the next garbage collection, finalizable objects that have been removed from the FReachable queue can finally be removed from memory at the next garbage collection cycle. Unreachable normal objects are removed in one garbage collection cycle. However, unreachable finalizable objects require at least two garbage collection cycles. This is part of the expense of using finalizable objects. Finalizable objects should not have a deep object graph. The finalizable object is kept not only in memory but also in any object it references.

7-2 shows the garbage collection cycle for two groups of objects. F is a finalizable object that references objects G and H. I is a non-finalizable object that references J and K. G, H, J, and K are non-finalizable objects.

Figure 7-2

Figure 7-2. Garbage collection cycle for finalizable object.

The IDisposable.Dispose method is an alternative to the Finalize method in non-deterministic garbage collection. Contrary to the Finalize method, IDisposable.Dispose is deterministic, called on demand, and has no latency.

Disposable Objects

Disposable objects implement the IDisposable interface, which has a single method—Dispose. The Dispose method is called deterministic or on demand. In the Dispose method, you can clean up for resources used by the object. Unlike the Finalize method, both the managed and unmanaged resources can be referenced in the Dispose method. Because the Dispose method is called on demand, you know the sequence of cleanup. Therefore, you know which objects have been previously cleaned up or not.

You can implement the Dispose method without inheriting the IDisposable interface. That is not the same as implementing the IDisposable interface and the resulting object is not a disposable object. The IDisposable interface is a marker indicating that the object is disposable. The Framework Class Library (FCL) relies on this marker to automatically call the Dispose method. For example, some .NET collections detect disposable objects to perform proper cleanup when the collection is disposed.

The method name Dispose is not the most transparent in all circumstances. Long-standing terminology or domain-specific phraseology may dictate using a different term. Most frequently, the alternate method name is Close. Whatever name is chosen, the method should delegate to the Dispose method. The user had the option to use the alternate name or the standard name, which is Dispose. The File.Close method is an example of using a different method name for deterministic cleanup. Avoid using alternate names for disposal unless there is a close affinity of the term with that type of object.

You can implement both the Finalize method for non-deterministic garbage collection and the Dispose method. Because you can clean up both managed and unmanaged resources there, the implementation of the Dispose method is usually a superset of the Finalize method. The Finalize method is limited to cleaning up unmanaged resources. In the Dispose method, call the GC.SuppressFinalize method. This method will remove the reference to the current object from the Finalization queue to avoid further overhead related to finalization. When both are implemented, the Finalize method is essentially a safety net if the Dispose method is not called.

To avoid inadvertently not calling the Dispose method on a disposable object, employ the using statement. Disposable objects defined in the using statement are automatically disposed of at the end of the using block. The Dispose method is called on those objects as the using block is exited. See the following code. In this code, obj1 is a disposable object. The Dispose method is called after the using block is exited.

using( XClass obj1) {
}
// obj1 disposed.

Next is a more complex using statement. You can list more than one disposable object within a comma-delimited list in the using statement. Within a single using statement, you can define multiple instances of the same type. For disposing different types, precede the using block with more than one using statement—one for each type. In the following code, there are two using statements. There is one using statement for the XClass type, while the other is for the YClass. In total, three instances are defined. The Dispose method of the three objects is automatically called at the end of the using block.

using( XClass obj1=new XClass(),
              obj2=new XClass())
using (YClass obj3 = new YClass())
{

}
// Dispose method called on obj1, obj2, and obj3

Dispose Pattern

Implementing proper disposal in a managed class can be non-trivial. When there is a base and derived types that are both disposable, the implementation can be even more complex. The dispose pattern is more than a pattern for implementing the Dispose method. It is the best practice for implementing deterministic and non-deterministic behavior and cleanup for a base and derived class. This relationship must be considered to implement the proper cleanup behavior. For easier understanding, the base and derive class implementation of the dispose pattern are presented separately in this chapter.

The dispose pattern has four primary goals: correctness, efficiency, robustness, and code reuse. Correctness is the goal for every pattern. The dispose pattern is the perspective from Microsoft on the correct implementation of the Dispose and Finalize methods. A disposed object is probably not immediately collected. For that reason, it remains available to the application. You should be able to call the Dispose method and other methods on a disposed object with predictable results. The dispose pattern provides robust behavior for disposed objects. The dispose pattern is refactored for code reuse to prevent redundant code. Redundant code is hard to maintain, and it is a place where problems can flourish.

The base class (XParent) implementation for the dispose pattern is as follows:

  • In the dispose pattern, the base class implements two Dispose methods. The protected Dispose method performs the actual resource cleanup. At the start of the method, a flag (disposed) is set to indicate that the object is disposed. The only parameter (disposing) indicates whether the cleanup is deterministic or non-deterministic. If disposing is true, it is deterministic and the Dispose has been called programmatically. You can clean up both managed and unmanaged resources. If false, you are restricted to the cleanup of unmanaged resources.

  • The second Dispose method, which is part of the public interface for the class, is called to initiate deterministic cleanup. It delegates to the one-parameter Dispose method to perform the actual cleanup. The parameter is set to true to indicate deterministic garbage collection. Because the cleanup has been performed, the Dispose method invokes GC.SuppressFinalize and removes a reference to a disposed object from the Finalization queue. This prevents further costs from finalization.

  • BaseFunction represents any method of the class. Methods of a disposable object should be callable even after the object is disposed. In the method, check if the object is disposed first. If so, throw the object-disposed exception. This is demonstrated in BaseFunction.

  • The base class destructor (~XParent) delegates to the one-parameter Dispose method also. However, the parameter is false to indicate non-deterministic garbage collection.

    // Base class
    
    public class XParent: IDisposable {
    
        // Deterministic garbage collection
    
        public void Dispose() {
    
            // if object disposed, throw exception.
    
            if (disposed) {
                throw new ObjectDisposedException("XParent");
            }
    
            // Call the general Dispose routine
    
            Dispose(true);
    
            // Collection already performed. Suppress further finalization.
    
            GC.SuppressFinalize(this);
    }
    
    // dispose property true if object has been disposed.
    
    protected bool disposed = false;
    
    // Deterministic and non-determenistic garbage collection
    // disposing parameter = true ( determinstic )
    //                       false (non-deterministic)
    
    protected virtual void Dispose(bool disposing) {
    
        disposed = true;
    
        if (disposing) {
    
            // if deterministic garbage collection, cleanup
            // managed resources.
        }
    
            // cleanup unmanaged resources.
        }
    
        // Representative of any base class method
    
        public void BaseFunction() {
    
            // if object disposed, throw exception.
    
            if (disposed) {
                throw new ObjectDisposedException("XParent");
            }
    
            // implement method behavior
       }
    
       // Non-deterministic garbage collection
    
       ~XParent() {
    
          // Call the general Dispose routine
    
          Dispose (false);
       }
    }

The child class (XChild) implementation is as follows.

  • The child class inherits the public Dispose method (parameterless) and the disposed property.

  • The one-parameter Dispose method is overriden in the child class to clean up for child resources. The overriden function is almost identical to the version in the parent. The only other difference is that this version calls the base class Dispose method. This affords the base class an opportunity to clean up for its resources.

  • DerivedFunction represents any method of the child class. In the method, you must check whether the object is disposed. If so, throw the object-disposed exception.

  • The child class destructor (~XChild) delegates to the one-parameter Dispose method for proper cleanup.

// Derived class

public class XDerived: XParent {

    // Deterministic and non-determenistic garbage collection
    // disposing parameter = true ( determinstic )
    //                       false (non-deterministic)

    protected override void Dispose(bool disposing) {
        disposed = true;

    if (disposing)
    {
        // if deterministic garbage collection, cleanup
        // managed resources.
    }

    // Call base class Dispose method for base class cleanup.

    base.Dispose(disposing);

    // cleanup unmanaged resources of derived class.
}

// Representative of any derived class method

public void DerivedFunction() {

    // if object disposed, throw exception.

    if (disposed) {
        throw new ObjectDisposedException("XChild");
    }
    // implement method behavior
}

// Non-deterministic garbage collection

~XDerived(){

    // Call the general Dispose routine

    Dispose(false);
  }
}

Weak References

There are strong and weak references. Until now, this chapter has focused on strong references. Both weak and strong references are created with the new operator. The difference is how a weak reference is collected unlike a strong reference. A strong reference cannot be collected unless unreachable. This is within the control of the application and not the Garbage Collector. A weak reference, unlike a strong reference, can be collected at the discretion of the Garbage Collector.

In managed code, strong references are the default reference. There is a strong commitment from the Garbage Collector to keep the associated object in memory—no exceptions or flexibility. Conversely, a weak reference has a weak commitment from the Garbage Collector. The Garbage Collector has the flexibility to remove the weakly referenced object from the managed heap when memory stress is applied to the application and more memory is needed.

Weak references represent the best of both worlds. Both the application and the Garbage Collector can access the weakly referenced object. If not collected, the application can continue to use the object referenced by the weak reference. In addition, the Garbage Collector can collect the weak reference whenever needed.

Weak references are ideal for objects that require a lot of memory and are persistent in some manner. For example, you could have an application that maintains large spreadsheets that is cached to a permanent or temporary file. Large spreadsheets that consist of hundreds of rows and columns are memory intensive. Naturally, the application performance improves when the spreadsheet is memory resident. However, that applies considerable memory stress. The Garbage Collector should have the option to remove the spreadsheet object if necessary. The spreadsheet object is the perfect candidate for a weak reference. This would keep the spreadsheet object in memory, and accessible by the application, but also collectible by the Garbage Collector, if needed. If collected, the application could easily rehydrate the spreadsheet from the backing file.

Weak references are also ideal for maintaining caches. Cache can be memory intensive. The weak reference can be used to vary the lifetime of the cache based on a time-out, variables, or other criteria. For example, a cache may have a time-out. Before the time-out, the cache could be maintained as a strong reference. When the cache expires, it would be converted to a weak reference and be available for collection, if needed. If the cache is backed by a persistent source, such as a Microsoft SQL database, associating the cache with a weak reference is done to conserve memory resources as required.

There are two types of weak references: a short and long weak reference. A short weak reference is the default. With a short weak reference, the strong reference is released before finalization. For long weak references, the reference is tracked through finalization. More than extending the lifetime of the object reference, it allows the object to be resurrected.

Following are the steps for using a weak reference:

  1. Create a strong reference.

  2. Create a weak reference that is initialized with the strong reference. The default constructor creates a short weak reference.

  3. Set the strong reference to null.

  4. The weak reference is accessible from the WeakReference.Target property.

  5. If the WeakReference.Target property is null and the WeakReference.IsLive property is false, the weak reference has been collected and is no longer available.

  6. If the weak reference is available, assign the WeakReference.Target property to a strong reference, and then use the object.

  7. If the weak reference is no longer available, rehydrate the data from a persistent source. When you are finishing using the updated strong reference, reinitialize a weak reference with the new strong reference.

The following code is a partial listing from an application that uses a weak reference. The program displays an array of names that is read from a persistent file. The array is assigned to a weak reference. The hScrollBar1_Scroll function scrolls through the names. First the function creates a strong reference. This is the WeakReference.Target assignment. If null, the names array has been collected, and the weak reference is no longer available. If that occurs, the array is rehydrated with the GetNames function. At the end of the function, the names reference is assigned null. This negates the strong reference, which leaves the weak reference to control the lifetime of the array.

Name[] names = null;
WeakReference wk;
List<byte[]> data = new List<byte[]>();

private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
    names= (Name[]) wk.Target;
    if (null == names) {
        MessageBox.Show("Rehydrate");
        names=GetNames();
        wk.Target = names;
    }
    if (e.NewValue > names.Length) {
        return;
    }
    txtItem.Text = names[e.NewValue].first+" "+
        names[e.NewValue].last;
    names = null;
}