I track an object using WeakReference<T>
(short weak reference) in my class Foo
. This class has a destructor in which I need to access that tracked object. The object I track is also tracking Foo
using WeakReference<Foo>
.
So now I wonder, when exactly does "zeroing" of the WeakReference
happen? Do all WeakReference
get nulled before ANY finalizer is run, or do each of them get nulled right before the finalizer of the object they track is about to run?
UPDATE
Now I also wonder if maybe Mono
project can shed some light on this one (link 1, link 2). But I am a bit worried that maybe MS GC
and Mono GC
might approach this issue differently and be incompatible.
A weakly referenced object is cleared by the Garbage Collector when it's weakly reachable. Weak reachability means that an object has neither strong nor soft references pointing to it. The object can be reached only by traversing a weak reference.
A weak reference permits the garbage collector to collect the object while still allowing the application to access the object. A weak reference is valid only during the indeterminate amount of time until the object is collected when no strong references exist.
A weak reference is a way to have some pointer to an object that does have any reference (strong reference). In . NET, any normal reference to another object is a strong reference . That is, when you declare a variable of a type that is not a primitive/value type, you are declaring a strong reference.
Weak references and breaking cycles (C++/CX) A WeakReference object supports the Resolve method, which returns null if the object no longer exists, or throws an Platform::InvalidCastException if the object is alive but is not of type T .
I thought to write a little demo program that demonstrates the difference. Turned out to be a bit more challenging than I counted on. First necessary ingredient is to ensure that the finalizer thread can be slowed down so you can observe the value of WeakReference.IsAlive without risking it being affected by a race with the finalizer thread. So I used:
class FinalizerDelayer {
~FinalizerDelayer() {
Console.WriteLine("Delaying finalizer...");
System.Threading.Thread.Sleep(500);
Console.WriteLine("Delay done");
}
}
Then a little class that will be target of the WeakReference:
class Example {
private int instance;
public Example(int instance) { this.instance = instance; }
~Example() {
Console.WriteLine("Example {0} finalized", instance);
}
}
Then a program that demonstrates the difference between a long and a short weak reference:
class Program {
static void Main(string[] args) {
var target1 = new Example(1);
var target2 = new Example(2);
var shortweak = new WeakReference(target1);
var longweak = new WeakReference(target2, true);
var delay = new FinalizerDelayer();
GC.Collect(); // Kills short reference
Console.WriteLine("Short alive = {0}", shortweak.IsAlive);
Console.WriteLine("Long alive = {0}", longweak.IsAlive);
GC.WaitForPendingFinalizers();
Console.WriteLine("Finalization done");
GC.Collect(); // Kills long reference
Console.WriteLine("Long alive = {0}", longweak.IsAlive);
Console.ReadLine();
}
}
You must run this program so the debugger cannot affect the life-time of objects. Select the Release build and change a debugger setting: Tools + Options, Debugging, General, untick the "Suppress JIT optimization" option.
Turned out the finalization order of objects truly is non-deterministic. The order is different every time you run the program. We want the FinalizerDelayer object to get finalized first but that doesn't always happen. I think it is a side-effect to the built-in Address Space Layout Randomization feature, it makes managed code very hard to attack. But run it often enough and you'll eventually get:
Delaying finalizer...
Short alive = False
Long alive = True
Delay done
Example 1 finalized
Example 2 finalized
Finalization done
Long alive = False
Long story short:
Beware of a quirk when the object gets resurrected, moved back from the freachable queue to the normal heap when a strong reference is re-created. Not something I explored in this demo program but a long weak reference will be needed to observe that. The basic reason why you'd need a long weak reference.
You can verify for yourself with a simple test program. But I find the documentation for the WeakReference
type itself to be somewhat more clear than the page you were looking at.
In particular, the flag referred to as "short" and "long" in your linked page is called trackResurrection
in the actual constructor documentation. And the description for the parameter reads:
Indicates when to stop tracking the object. If true, the object is tracked after finalization; if false, the object is only tracked until finalization.
The "Remarks" section also reads:
If trackResurrection is false, a short weak reference is created. If trackResurrection is true, a long weak reference is created.
This confirms that when you use a "short" weak reference, a finalized object will be no longer tracked (i.e. the Target
becomes null
) by the WeakReference
object, but when you use a "long" weak reference, it will be.
For both kinds of weak reference, an object that has actually been garbage-collected will for sure no longer be tracked (obviously).
In general, no other thread in your program should be able to observe an object while the finalizer thread is actually doing its work, so the precise moment for the "short" weak reference when the Target
property is set to null
seems moot to me. If some other thread in your program observes the value as non-null, the finalizer hasn't been run yet. If it observes it as null, the finalizer has run. That "other thread" should not itself run while the finalizer thread is working, so finalization should essentially be atomic as far as that "other thread" is concerned.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With