我在班级WeakReference<T>
中使用Foo
(short weak reference)跟踪对象。这个类有一个析构函数,我需要在其中访问该跟踪对象。我跟踪的对象也使用Foo
跟踪WeakReference<Foo>
。
所以现在我想知道,究竟是什么时候&#34;归零&#34; WeakReference
发生了什么?在运行任何终结器之前,是否所有WeakReference
都会被取消,或者在它们跟踪的对象的终结器即将运行之前,它们是否都会被取消?
更新
现在我也想知道,Mono
项目是否可以为这个项目提供一些启示(link 1,link 2)。但我有点担心,MS GC
和Mono GC
可能会以不同的方式处理此问题并且不兼容。
答案 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完成了 完成定稿
长活=假
长话短说:
当对象复活时要小心一个怪癖,当重新创建一个强引用时,将其从可释放队列移回正常堆。这不是我在这个演示程序中探索过的东西,但需要一个很长的弱引用来观察它。你需要长期弱参考的基本原因。
答案 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 Framework(part 1讨论了复活和可释放的队列):
现在,这是垃圾收集(GC)运行时发生的情况:
垃圾收集器构建了所有可到达对象的图形。本文的第1部分讨论了它的工作原理。
垃圾收集器扫描短的弱引用表。如果表中的指针引用的对象不是图的一部分,则指针标识无法访问的对象,短弱引用表中的插槽设置为空。
垃圾收集器扫描终结队列。如果队列中的指针引用的对象不是图形的一部分,则指针标识不可到达的对象,并且指针从完成队列移动到可释放的队列。此时,该对象被添加到图形中,因为该对象现在被认为是可到达的。
垃圾收集器扫描长弱引用表。如果表中的指针引用的对象不是图的一部分(现在包含可释放队列中条目指向的对象),则指针标识无法访问的对象,并将该槽设置为空。
- 醇>
垃圾收集器压缩内存,挤出无法到达的物体留下的孔。
所以即使我的类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>源代码