I modified Chapter 5 from Writing High-Performance .NET Code and posted it as an article at CodeProject. Take a look and tell me what you think!
Monthly Archives: August 2014
Short vs. Long Weak References and Object Resurrection
Last time, I talked about the basics of using WeakReference, what they meant and how the CLR treats them. Today in part 2, I’ll discuss some important subtleties. Part 3 of this series can be found here.
Short vs. Long Weak References
First, there are two types of weak references in the CLR:
- Short – Once the object is reclaimed by garbage collection, the reference is set to null. All of the examples in the previous article, with WeakReference and WeakReference<T>, were examples of short weak references.
- Long – If the object has a finalizer AND the reference is created with the correct options, then the reference will point to the object until the finalizer completes.
Short weak references are fairly easy to understand. Once the garbage collection happens and the object has been collected, the reference gets set to null, the end. A short weak reference can only be in one of two states: alive or collected.
Using long weak references is more complicated because the object can be in one of three states:
- Object is still fully alive (has not been promoted or garbage collected).
- Object has been promoted and the finalizer has been queued to run, but has not yet run.
- The object has been cleaned up fully and collected.
With long weak references, you can retrieve a reference to the object during stages 1 and 2. Stage 1 is the same as with short weak references, but stage 2 is tricky. Now the object is in a possibly undefined state. Garbage collection has started, and as soon as the finalizer thread starts running pending finalizers, the object will be cleaned up. This can happen at any time, so using the object is very tricky. The weak reference to the target object remains non-null until the target object’s finalizer completes.
To create a long weak reference, use this constructor:
WeakReference<MyObject> myRefWeakLong = new WeakReference<MyObject>(new MyObject(), true);
The true argument specifies that you want to track resurrection. That’s a new term and it is the whole point of long weak references.
Aside: Resurrection
First, let me say this up front: Don’t do this. You don’t need it. Don’t try it. You’ll see why. I don’t know if there is a special reason why resurrection is allowed in .NET, or it’s just a natural consequence of how garbage collection works, but there is no good reason to do something like this.
So here’s what not to do:
class Program { class MyObject { ~MyObject() { myObj = this; } } static MyObject myObj = new MyObject(); static void Main(string[] args) { myObj = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
By setting the myObj reference back to an object, you are resurrecting that object. This is bad for a number of reasons:
- You can only resurrect an object once. Because the object has already been promoted to gen 1 by the garbage collector, it has a guaranteed limited lifetime.
- The finalizer will not run again, unless you call GC.ReRegisterForFinalize() on the object.
- The state of the object can be indeterminate. Objects with native resources will have released those resources and they will need to be reinitialized. It can be tricky picking this apart.
- Any objects that the resurrected object refers to will also be resurrected. If those objects have finalizers they will also have run, leaving you in a questionable state.
So why is this even possible? Some languages consider this a bug, and you should to. Some people use this technique for object pooling, but this is a particularly complex way of doing it, and there are many better ways. You should probably consider object resurrection a bug as well. If you do happen upon a legitimate use case for this, you should be able to fully justify it enough to override all of the objections here.
Weak vs. Strong vs. Finalizer Behavior
There are two dimensions for specifying a WeakReference<T>: the weak reference’s creation parameters and whether the object has a finalizer. The WeakReference’s behavior based on these is described in this table:
No finalizer | Has finalizer | |
trackResurrection = false | short | short |
trackResurrection = true | short | long |
An interesting case that isn’t explicitly specified in the documentation is when trackResurrection is false, but the object does have a finalizer. When does the WeakReference get set to null? Well, it follows the rules for short weak references and is set to null when the garbage collection happens. Yes, the object does get promoted to gen 1 and the finalizer gets put into the queue. The finalizer can even resurrect the object if it wants, but the point is that the WeakReference isn’t tracking it–because that’s what you said when you created it. WeakReference’s creation parameters do not affect how the garbage collector treats the target object, only what happens to the WeakReference.
You can see this in practice with the following code:
class MyObjectWithFinalizer { ~MyObjectWithFinalizer() { var target = myRefLong.Target as MyObjectWithFinalizer; Console.WriteLine("In finalizer. target == {0}", target == null ? "null" : "non-null"); Console.WriteLine("~MyObjectWithFinalizer"); } } static WeakReference myRefLong = new WeakReference(new MyObjectWithFinalizer(), true); static void Main(string[] args) { GC.Collect(); MyObjectWithFinalizer myObj2 = myRefLong.Target as MyObjectWithFinalizer; Console.WriteLine("myObj2 == {0}", myObj2 == null ? "null" : "non-null"); GC.Collect(); GC.WaitForPendingFinalizers(); myObj2 = myRefLong.Target as MyObjectWithFinalizer; Console.WriteLine("myObj2 == {0}", myObj2 == null ? "null" : "non-null"); }
The output is:
myObj2 == non-null In finalizer. target == non-null ~MyObjectWithFinalizer myObj2 == null
Finding Weak References in a Debugger
Windbg can show you how to find where your weak references, both short and long.
Here is some sample code to show you what’s going on:
using System; using System.Diagnostics; namespace WeakReferenceTest { class Program { class MyObject { ~MyObject() { } } static void Main(string[] args) { var strongRef = new MyObject(); WeakReference<MyObject> weakRef = new WeakReference<MyObject>(strongRef, trackResurrection: false); strongRef = null; Debugger.Break(); GC.Collect(); MyObject retrievedRef; // Following exists to prevent the weak references themselves // from being collected before the debugger breaks if (weakRef.TryGetTarget(out retrievedRef)) { Console.WriteLine(retrievedRef); } } } }
Compile this program in Release mode.
In Windbg, do the following:
- Ctrl+E to execute. Browse to the compiled program and open it.
- Run command: sxe ld clrjit (this tells the debugger to break when the clrjit.dll file is loaded, which you need before you can execute .loadby)
- Run command: g
- Run command .loadby sos clr
- Run command: g
- The program should now break at the Debugger.Break() method.
- Run command !gchandles
You should output similar to this:
0:000> !gchandles
Handle Type Object Size Data Type
011112f4 WeakShort 02d324b4 12 WeakReferenceTest.Program+MyObject
011111d4 Strong 02d31d70 36 System.Security.PermissionSet
011111d8 Strong 02d31238 28 System.SharedStatics
011111dc Strong 02d311c8 84 System.Threading.ThreadAbortException
011111e0 Strong 02d31174 84 System.Threading.ThreadAbortException
011111e4 Strong 02d31120 84 System.ExecutionEngineException
011111e8 Strong 02d310cc 84 System.StackOverflowException
011111ec Strong 02d31078 84 System.OutOfMemoryException
011111f0 Strong 02d31024 84 System.Exception
011111fc Strong 02d3142c 112 System.AppDomain
011113ec Pinned 03d333a8 8176 System.Object[]
011113f0 Pinned 03d32398 4096 System.Object[]
011113f4 Pinned 03d32178 528 System.Object[]
011113f8 Pinned 02d3121c 12 System.Object
011113fc Pinned 03d31020 4424 System.Object[]
Statistics:
MT Count TotalSize Class Name
70e72554 1 12 System.Object
01143814 1 12 WeakReferenceTest.Program+MyObject
70e725a8 1 28 System.SharedStatics
70e72f0c 1 36 System.Security.PermissionSet
70e724d8 1 84 System.ExecutionEngineException
70e72494 1 84 System.StackOverflowException
70e72450 1 84 System.OutOfMemoryException
70e722fc 1 84 System.Exception
70e72624 1 112 System.AppDomain
70e7251c 2 168 System.Threading.ThreadAbortException
70e35738 4 17224 System.Object[]
Total 15 objects
Handles:
Strong Handles: 9
Pinned Handles: 5
Weak Short Handles: 1
The weak short reference is called a “Weak Short Handle” in this output.
Next Time
The first article explained how WeakReference works, and this one explained a few of the subtleties, including some behavior you probably don’t want to use. Next time, I’ll go into why you would want to use WeakReference in the first place, and provide a sample application.
Prefer WeakReference<T> to WeakReference
In Writing High-Performance .NET Code, I mention the WeakReference type briefly in Chapter 2 (Garbage Collection), but I don’t go into it too much. However, for the blog, I want to start a small series of posts going into some more detail about WeakReference, how it works, and when to use it, with some example implementations. In this first post, I’ll just cover what it is, what options there are, and how to use it.
A WeakReference is a reference to an object, but one that still allows the garbage collector to destroy the object and reclaim its memory. This is in contrast to a strong (i.e., normal) reference that does prevent the GC from cleaning up the object.
There are two versions of WeakReference:
First, let’s take a look a WeakReference, which has been around since .NET 1.1.
You allocate a weak reference like this:
var weakRef = new WeakReference(myObj); myObj = null;
myObj is an existing object of your choice. Once you assign it to the weakRef variable, you should set the original strong reference to null (otherwise, what’s the point?). Now, whenever there is a garbage collection the object weakRef is referring to may be collected. To access this object, you may be tempted to make use WeakReference’s IsAlive property, as in this example:
WeakReference ref1 = new WeakReference(new MyObject()); if (ref1.IsAlive) { // wrong! DoSomething(ref1.Target as MyObject); }
IsAlive is a rather silly property. If it returns false, it’s fine–you know the object has been collected. However, if it returns true, then you don’t actually know anything useful because the object could still be garbage collected before you get a chance to make a strong reference to it!
The correct way to use this is to ignore the IsAlive property completely and just attempt to make a strong reference from Target:
MyObject obj = ref1.Target as MyObject; if (obj != null) { // correct DoSomething(obj); }
Now there is no race. If obj ends up being non-null, then you’ve got a strong reference that is guaranteed to not be garbage collected (until your own strong reference goes out of scope).
Recognizing some of the silliness and umm…weakness of WeakReference, WeakReference<T> was introduced in .NET 4.5 and it formalizes the above procedure by removing both the Target and IsAlive properties from the class and providing you with these two methods:
- SetTarget – Set a new target object
- TryGetTarget – Attempt to retrieve the object and assign it to a strong reference
This example demonstrates the usage, which is essentially the same as the correct way to use WeakReference from above:
WeakReference<MyObject> ref2 = new WeakReference<MyObject>(new MyObject()); MyObject obj2; if (ref2.TryGetTarget(out obj2)) { DoSomething(obj2); }
You could also ask yourself: Why is there a SetTarget method at all? After all, you could just allocate a new WeakReference<T>.
If you are using WeakReference<T> at all, it likely means you are somewhat memory conscious . In this case, allocating new WeakReference<T> objects will contribute extra, unnecessary memory pressure, potentially worsening the overall performance. Why do this, when you can just reuse the WeakReference<T> object for new targets as needed?
Next time, more details on weak references, particularly the differences between short and long weak references, and taking a peek at them in the debugger. We’ll also cover when you should actually use WeakReference<T> at all.
Part 2, Short vs. Long Weak References and Object Resurrection, is up.
Part 3, Practical Uses, is up.