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

时间:2015-03-19 07:49:13

标签: c# memory-management garbage-collection weak-references

我在班级WeakReference<T>中使用Fooshort weak reference)跟踪对象。这个类有一个析构函数,我需要在其中访问该跟踪对象。我跟踪的对象也使用Foo跟踪WeakReference<Foo>

所以现在我想知道,究竟是什么时候&#34;归零&#34; WeakReference发生了什么?在运行任何终结器之前,是否所有WeakReference都会被取消,或者在它们跟踪的对象的终结器即将运行之前,它们是否都会被取消?

更新

现在我也想知道,Mono项目是否可以为这个项目提供一些启示(link 1link 2)。但我有点担心,MS GCMono GC可能会以不同的方式处理此问题并且不兼容

3 个答案:

答案 0 :(得分:5)

我想写一个演示程序来演示差异。原来比我指望的更具挑战性。第一个必要的成分是确保终结器线程可以减慢速度,这样你就可以观察到WeakReference.IsAlive的值,而不会有被终结器线程影响的风险。所以我用过:

class FinalizerDelayer {
    ~FinalizerDelayer() {
        Console.WriteLine("Delaying finalizer...");
        System.Threading.Thread.Sleep(500);
        Console.WriteLine("Delay done");
    }
}

然后是一个将成为WeakReference目标的小课程:

class Example {
    private int instance;
    public Example(int instance) { this.instance = instance; }
    ~Example() {
        Console.WriteLine("Example {0} finalized", instance);
    }
}

然后是一个演示长弱引用之间差异的程序:

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();
    }
}

您必须运行此程序,以便调试器不会影响对象的生命周期。选择Release build并更改调试器设置:Tools + Options,Debugging,General,取消选中&#34;抑制JIT优化&#34;选项。

原来对象的终结顺序确实是非确定性的。每次运行程序时,顺序都不同。我们希望FinalizerDelayer对象首先完成,但并不总是这样。我认为它是内置地址空间布局随机化功能的副作用,它使托管代码很难攻击。但经常运行它,你最终会得到:

  

延迟终结者......
      活着短=假       长活=真实       延迟完成
      例1完成了       例2完成了       完成定稿
      长活=假

长话短说:

  • 只要收集对象并将其置于可释放队列中,就可以将IsAlive设置为 false ,以便最终确定。该对象仍然存在,但不再存在强引用,它很快就会完成。
  • 长弱引用会一直跟踪对象的真实生命周期,包括它在可释放队列中的生命周期。在完成终结工具之前,IsAlive不会被设置为 false

当对象复活时要小心一个怪癖,当重新创建一个强引用时,将其从可释放队列移回正常堆。这不是我在这个演示程序中探索过的东西,但需要一个很长的弱引用来观察它。你需要长期弱参考的基本原因。

答案 1 :(得分:4)

您可以使用简单的测试程序自行验证。但我发现WeakReference类型本身的文档比您正在查看的页面更清晰。

特别是,链接页面中称为“短”和“长”的标记在the actual constructor documentation中称为trackResurrection。参数的描述如下:

  

指示何时停止跟踪对象。如果为true,则在完成后跟踪对象;如果为false,则仅跟踪对象直到完成。

“备注”部分还包括:

  

如果trackResurrection为false,则会创建一个简短的弱引用。如果trackResurrection为true,则会创建一个长弱引用。

这确认当您使用“短”弱引用时,Target对象将不再跟踪已完成的对象(即null变为WeakReference),但是你使用“长”弱引用,它将是。

对于这两种弱引用,实际上已被垃圾收集的对象肯定不再被跟踪(显然)。

通常,在终结器线程实际执行其工作时,程序中没有其他线程能够观察对象,因此设置Target属性时“短”弱引用的精确时刻到null对我来说似乎没有实际意义。如果程序中的某个其他线程将该值观察为非null,则终结器尚未运行。如果它将其视为null,则终结器已运行。当终结器线程正在工作时,“其他线程”本身不应该运行,因此就“其他线程”而言,终结应该基本上是原子的。

答案 2 :(得分:0)

经过大量研究后,我找到了这篇古老的文章Garbage Collection Part 2: Automatic Memory Management in the Microsoft .NET Frameworkpart 1讨论了复活和可释放的队列):

  

现在,这是垃圾收集(GC)运行时发生的情况:

     
      
  1. 垃圾收集器构建了所有可到达对象的图形。本文的第1部分讨论了它的工作原理。

  2.   
  3. 垃圾收集器扫描短的弱引用表。如果表中的指针引用的对象不是图的一部分,则指针标识无法访问的对象,短弱引用表中的插槽设置为空

  4.   
  5. 垃圾收集器扫描终结队列。如果队列中的指针引用的对象不是图形的一部分,则指针标识不可到达的对象,并且指针从完成队列移动到可释放的队列。此时,该对象被添加到图形中,因为该对象现在被认为是可到达的。

  6.   
  7. 垃圾收集器扫描长弱引用表。如果表中的指针引用的对象不是图的一部分(现在包含可释放队列中条目指向的对象),则指针标识无法访问的对象,并将该槽设置为空。

  8.   
  9. 垃圾收集器压缩内存,挤出无法到达的物体留下的孔。

  10.   

所以即使我的类Foo有一个终结器,因此它将位于一个可释放的队列中(被认为是一个根) - 在这个对象扎根于freachable之前,就会发生短弱引用的解析queue,表示短弱引用为null:

  只要确定对象无法访问,

垃圾收集器就会在短弱引用表中将指针设置为null。如果对象具有Finalize方法,则尚未调用该方法,因此该对象仍然存在。如果应用程序访问WeakReference对象的Target属性,则即使该对象实际仍存在,也将返回null。


此外,如Yun Jin's WebLog所述,在终结器中引用可终结对象通常并不好,但WeakReference有点例外(尽管this was not always the case)。由于WeakReference是具有终结器的对象,如果在Foo的终结器中访问它,它可能已经完成(在这种情况下,Target属性将始终返回null,尽管事实是被跟踪的对象可能仍然活得很好(more info)。


我刚刚确认Mono garbage collector与此行为一致。


有用的参考:
- WeakReference源代码
- WeakReference<T>源代码