简体   繁体   中英

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 .

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.

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 (ie 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.

So after much research, I was able to find this ancient article Garbage Collection Part 2: Automatic Memory Management in the Microsoft .NET Framework ( part 1 discusses resurrection and freachable queue):

Now, here's what happens when a garbage collection (GC) runs:

  1. The garbage collector builds a graph of all the reachable objects. Part 1 of this article discussed how this works.

  2. The garbage collector scans the short weak reference table. If a pointer in the table refers to an object that is not part of the graph , then the pointer identifies an unreachable object and the slot in the short weak reference table is set to null .

  3. The garbage collector scans the finalization queue . If a pointer in the queue refers to an object that is not part of the graph, then the pointer identifies an unreachable object and the pointer is moved from the finalization queue to the freachable queue. At this point, the object is added to the graph since the object is now considered reachable.

  4. The garbage collector scans the long weak reference table. If a pointer in the table refers to an object that is not part of the graph (which now contains the objects pointed to by entries in the freachable queue), then the pointer identifies an unreachable object and the slot is set to null.

  5. The garbage collector compacts the memory, squeezing out the holes left by the unreachable objects.

So even though my class Foo has a finalizer and thus it will be in a freachable queue (which is considered to be a root) - the resolving of short weak references happens BEFORE this object gets rooted in the freachable queue, which means the short weak reference will be null:

garbage collector sets the pointer to null in the short weak reference table as soon as it has determined that the object is unreachable. If the object has a Finalize method, the method has not been called yet so the object still exists. If the application accesses the WeakReference object's Target property, then null will be returned even though the object actually still exists.


Furthermore, as mentioned on Yun Jin's WebLog , it is generally not good to reference finalizable objects in finalizers, but WeakReference is a bit of an exception (though this was not always the case ). Since WeakReference is an object with finalizer, if it's accessed in finalizer of Foo , it might have been already finalized (in which case the Target property will always return null, despite the fact that the tracked object might still be alive and well ( more info ).


I have just confirmed that Mono garbage collector is consistent with this behavior.


Useful reference:
- WeakReference source code
- WeakReference<T> source code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM