WeakReferences, Memory Management, Garbage, and More...

A short discussion of the Garbage Collection process in .NET, how it relates to WeakReferences, and other cool stuff.

A discussion of WeakReferences must, by definition, be preceded by some short discussion about the .Net Garbage Collection process, since the whole concept of weak references revolves around memory conservation.

In .NET, the garbage collector uses what is called a "mark and compact" algorithm. At the start of a cycle, it identifies all root references to objects in memory. These are references from static variables, CPU registers, local values or parameter instances, and f-reachable (finalization queue) objects. Using this list, the GC is able to recursively construct a graph of all reachable objects.

What the GC then does is to compact all reachable objects next to each other, very much like disk defragmentation, which has the effect of overwriting unreachable objects. This requires the maintenance of a consistent state, and so all managed threads are halted during collections. The short pauses this creates are generally insignificant unless a very large garbage collection cycle is necessary. In order to reduce the likelihood of a collection cycle at a bad time, the System.GC class has a Collect method that can be called immediately before any mission-critical code is called. This doesn't prevent the GC from running, but it does reduce the chance that it will. Unfortunately, many developers, finding the Collect method, start to abuse it, often not fully understanding exactly what the semantics of the whole process are. For example forcing a Garbage Collection is not a band-aid for your lack of knowledge about how to release COM references to Excel!

Basically, in .NET, we don't control the Garbage Collection process. For coders coming from a non-managed-code background or who are control freaks, this may be difficult to come to terms with. Get over it.

The GC does not normally clean up all garbage during a pass. Studies have shown that recently created objects are more likely to need collection. Therefore, the GC is generational, attempting to clean up short-lived objects more frequently. It does this by doing its work in three passes, and each time an object survives a generation, it is moved to the next generation. So, the GC runs more frequently in Gen 0 than in Gen 1 and Gen 2.

The garbage collector cannot collect an object in use by an application while the application's code can reach that object. The application is said to have a strong reference to the object.

A weak reference allows the garbage collector to collect the object while still allowing the application to access the object. It is valid only during the indeterminate amount of time until the object is collected -- when no strong references exist. When you use a weak reference, the application can still obtain a strong reference to the object, which prevents it from being collected. However, there is always the possibility that the garbage collector will get to the object first before a strong reference is reestablished.

Weak references are useful for objects that use a lot of memory, but can be recreated easily if they are reclaimed by garbage collection.

Let's say you have a large DataSet in an application that holds a complex hierarchical choice of items for some particular business logic . If the underlying data is large, keeping the DataSet in memory is inefficient when the user is involved with something else in the application.

When the user switches away to another part of the application, you can use the WeakReference class to create a weak reference to the DataSet and destroy all strong references. When the user switches back to the part of your application that uses the large DataSet, the application attempts to obtain a strong reference to it and, if successful, it avoids reconstructing the DataSet.

To create a weak reference with an object, you create a WeakReference using the instance of the object to be tracked. You then set the Target property to that object and you set the object itself to null.

Short and Long Weak References

You can create a short weak reference or a long weak reference:

  • Short

    The target of a short weak reference becomes null when the object is reclaimed by garbage collection. The weak reference is itself a managed object, and is subject to garbage collection just like any other managed object. A short weak reference is the default constructor for WeakReference.

  • Long

    A long weak reference is retained after the object's Finalize method has been called. This allows the object to be recreated, but the state of the object remains unpredictable. To use a long reference, you specify true in the WeakReference constructor.

    If the object's type does not have a Finalize method, the short weak reference functionality applies and the weak reference is valid only until the target is collected, which can occur anytime after the finalizer is run.

To establish a strong reference and use the object again, cast the Target property of a WeakReference to the type of the object. If the Target property returns null, the object was collected and you need to re-create it. Otherwise, you can continue to use the object because the application has regained a strong reference to it.

When should you use Weak References?

Use long weak references only when necessary as the state of the object is unpredictable after finalization.

You should avoid using weak references to small objects because the pointer itself may be as large or larger than the object in memory.

Avoid using weak references as an automatic solution to memory management problems. Instead, develop an effective caching policy for handling your application's objects.

I saw a classic example of not properly dealing with memory management via the logic of the application as a first goal, in a recent newsgroup post in the C# language group. The poster was asking how to switch the CLR version from the default Workstation to Server in order to have more than one thread for the GC on a server with multiple CPU's.

I responded that the first priority would be to find out why you have such a memory run-up in your app, rather than attempting to band-aid it with different GC Workstation/ Server models.

Willy DeNoyette, one of the real .NET gurus who populates the C# group, responded that in addition to what I had said, it would not help you to switch to the server GC, nor can you expect a better performance from the server GC in V1.0 of the framework. He suggested that the user had a memory allocation issue because of memory fragmentation, notably  LOH fragmentation (There is a nice MSDN Mag article that covers Large Object Heap fragmentation, OOM exceptions, and profiling ).

Finally the original poster indicated the memory builds up when they receive a load of market data which they store in the .net Queue object -- they have a background thread that dequeues the message and processes it.

Right there, it became obvious that the user should have been looking at how they handle threads with the queue. A common mistake is to poll the queue in a while loop, which can use up a lot of CPU waiting for there to be an object in the queue. Dan Schwieg has a nice article here that deals with this effectively, called the Blocking Queue. This is just one example of how to code defensively and use sensible memory - management practices, in addition to a well - thought - out caching mechanism, and WeakReferences add an additional piece of ammunition to your coding arsenal.

So let's take a look at some sample WeakReference code that can use WeakReferences as a sort of Memory Cache. Objects within the Cache are retrieved quickly, but if the Garbage Collector has recovered the memory they occupied, they will need to be re-created.

The basic pattern for the use of a WeakReference would look like this:

private WeakReference Data;

public DataSet GetData()
{
    DataSet data = (DataSet)Data.Target;
    if( data !=null)
      {
       return data;
      }
   else
     {
      data=  GetBigDataSet() // load the data ....
      // Create a Weak Reference to data for later use...
     Data.Target = data;
    }
    return data;
}

As can be seen from the above working code snippet, the pattern for the use of WeakReferences is quite simple.

Where you have the assignment of WeakReference "Data" you can check for garbage collection by checking for null. The key here is that you want to first assign the weak reference to a strong reference ( DataSet data=(DataSet)Data.Target ) to avoid the possibility that between the check for null and accessing the DataSet, the GC may have run and cleaned up your WeakReference. The strong reference prevents the GC from doing this, so it needs to be assigned first before checking data for null.

Summary

Weak references aren't "skinny 98 pound weakling" code at all. They can be an important  part of a carefully designed data and object caching strategy to minimize memory consumption within the overall .NET Framework paradigm to help create more performant applications.

By Peter Bromberg   Popularity  (2837 Views)