简体   繁体   English

什么时候短的弱引用变为空?

[英]When do short weak references become null?

I track an object using WeakReference<T> ( short weak reference ) in my class Foo . 我在类Foo使用WeakReference<T>短弱引用 )跟踪对象。 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> . 我跟踪的对象也使用WeakReference<Foo>跟踪Foo

So now I wonder, when exactly does "zeroing" of the WeakReference happen? 所以现在我想知道, WeakReference “归零”究竟发生了什么? 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? 在运行任何终结器之前,所有WeakReference都会被取消,或者在它们跟踪的对象的终结器即将运行之前,它们是否都会被取消?

UPDATE UPDATE

Now I also wonder if maybe Mono project can shed some light on this one ( link 1 , link 2 ). 现在我也想知道Mono项目是否可以为这个项目提供一些启示( 链接1链接2 )。 But I am a bit worried that maybe MS GC and Mono GC might approach this issue differently and be incompatible . 但我有点担心MS GCMono GC可能会以不同的方式解决这个问题并且不兼容

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. 第一个必要的成分是确保终结器线程可以减慢速度,这样你就可以观察到WeakReference.IsAlive的值,而不会有被终结器线程影响的风险。 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: 然后是一个将成为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. 选择Release build并更改调试器设置:Tools + Options,Debugging,General,取消选中“Suppress JIT optimization”选项。

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. 我们希望FinalizerDelayer对象首先完成,但并不总是这样。 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 例1完成
Example 2 finalized 例2完成
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. 只要收集对象并将其放置在可释放队列上,就可以将IsAlive设置为false ,以便最终确定它。 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. 在终结器完成之前,IsAlive不会设置为false

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. 但我发现WeakReference类型本身的文档比您正在查看的页面更清晰。

In particular, the flag referred to as "short" and "long" in your linked page is called trackResurrection in the actual constructor documentation . 特别是,链接页面中称为“short”和“long”的trackResurrection实际构造函数文档中称为trackResurrection And the description for the parameter reads: 参数说明如下:

Indicates when to stop tracking the object. 指示何时停止跟踪对象。 If true, the object is tracked after finalization; 如果为true,则在完成后跟踪对象; if false, the object is only tracked until finalization. 如果为false,则仅跟踪对象直到完成。

The "Remarks" section also reads: “备注”部分还内容如下:

If trackResurrection is false, a short weak reference is created. 如果trackResurrection为false,则会创建一个简短的弱引用。 If trackResurrection is true, a long weak reference is created. 如果trackResurrection为true,则创建一个长弱引用。

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. 这确认了当你使用“短”弱引用时, WeakReference对象将不再跟踪WeakReference对象(即Target变为null ),但是当你使用“long”弱引用时,它将是。

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. 通常,程序中没有其他线程能够在终结器线程实际执行其工作时观察对象,因此当Target属性设置为null时,“短”弱引用的精确时刻对我来说似乎没有意义。 If some other thread in your program observes the value as non-null, the finalizer hasn't been run yet. 如果程序中的某个其他线程将该值观察为非null,则终结器尚未运行。 If it observes it as null, the finalizer has run. 如果它将其视为null,则终结器已运行。 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): 因此经过大量研究后,我能够找到这篇古老的文章垃圾收集第2部分:Microsoft .NET Framework中的自动内存管理第1部分讨论了复活和可释放队列):

Now, here's what happens when a garbage collection (GC) runs: 现在,这是垃圾收集(GC)运行时发生的情况:

  1. The garbage collector builds a graph of all the reachable objects. 垃圾收集器构建所有可到达对象的图形。 Part 1 of this article discussed how this works. 本文的第1部分讨论了它的工作原理。

  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 . 如果表中的指针引用的对象不是图的一部分 ,则指针标识不可到达的对象, 并将短弱引用表中的槽设置为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. 如果表中的指针引用的对象不是图的一部分(现在包含可释放队列中的条目所指向的对象),则指针标识不可到达的对象,并将该槽设置为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: 所以,即使我的类Foo有一个终结器,因此它将在一个可释放的队列中(被认为是一个根) - 在这个对象扎根于可释放队列之前,就会发生短弱引用的解析,这意味着短暂的弱引用将为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. 垃圾收集器一旦确定对象不可达,就将指针设置为短弱引用表中的null。 If the object has a Finalize method, the method has not been called yet so the object still exists. 如果对象具有Finalize方法,则尚未调用该方法,因此该对象仍然存在。 If the application accesses the WeakReference object's Target property, then null will be returned even though the object actually still exists. 如果应用程序访问WeakReference对象的Target属性,则即使该对象实际仍存在,也将返回null。


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 ). 此外,正如Yun Jin的WebLog所提到 ,在终结器中引用可终结对象通常并不好,但WeakReference有点例外(尽管并非总是这样 )。 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 ). 由于WeakReference是一个带有终结器的对象,如果它在Foo终结器中被访问,它可能已经完成(在这种情况下, Target属性将始终返回null,尽管被跟踪对象可能仍然存活并且很好( 更多信息) )。


I have just confirmed that Mono garbage collector is consistent with this behavior. 我刚刚确认Mono垃圾收集器与此行为一致。


Useful reference: 有用的参考:
- WeakReference source code - WeakReference源代码
- WeakReference<T> source code - WeakReference <T>源代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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