Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do short weak references become null?

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.

like image 902
Paya Avatar asked Mar 19 '15 07:03

Paya


People also ask

How does a weak reference work?

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.

When should weak references be used in garbage collection?

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.

What is the difference between strong and weak references in C#?

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.

What is a weak reference in C++?

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 .


2 Answers

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:

  • A short weak reference sets IsAlive to false as soon as the object is collected and placed on the freachable queue, ready for it to be finalized. The object still physically exists but no strong references exist anymore, it will soon get finalized.
  • A long weak reference keeps track of the object all the way through its true life time, including its life on the freachable queue. IsAlive won't be set to false until its finalizer completed.

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.

like image 121
Hans Passant Avatar answered Oct 20 '22 05:10

Hans Passant


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.

like image 27
Peter Duniho Avatar answered Oct 20 '22 05:10

Peter Duniho