垃圾收集器依赖于JIT编译器提供的信息,告诉它哪些代码地址范围内的变量和"things"仍在使用中。因此,在您的代码中,由于不再使用对象变量,GC可以自由地收集它们。WeakReference无法防止这种情况发生,实际上,这就是WR的全部意义,允许您保留对对象的引用,同时不会阻止其被收集。关于
WeakReference对象的案例在MSDN上的一行描述中得到了很好的总结:“表示弱引用,它引用一个对象,同时仍然允许垃圾回收器回收该对象。” WeakReference对象不会被垃圾收集,因此您可以安全地使用它们,但是它们所引用的对象只剩下WR引用,因此可以自由收集。在通过调试器执行代码时,变量在作用域内人为地延长到其作用域结束,通常是它们声明的块的末尾(如方法),以便您可以在断点处检查它们。这里有一些微妙的事情需要发现。请考虑以下代码:
using System;
namespace ConsoleApplication20
{
public class Test
{
public int Value;
~Test()
{
Console.Out.WriteLine("Test collected");
}
public void Execute()
{
Console.Out.WriteLine("The value of Value: " + Value);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.Out.WriteLine("Leaving Test.Execute");
}
}
class Program
{
static void Main(string[] args)
{
Test t = new Test { Value = 15 };
t.Execute();
}
}
}
在 Release 模式下,没有调试器附加时,输出如下:
Value 的值:15
已收集测试
离开 Test.Execute
原因是尽管您仍在执行与 Test 对象相关联的方法,但在要求 GC 执行其操作时,不需要任何 Test 实例引用(没有对 this 或 Value 的引用),也没有调用任何实例方法剩余可执行,所以对象可以安全地进行回收。
如果您不知道这一点,可能会产生一些严重的副作用。
请考虑以下类:
public class ClassThatHoldsUnmanagedResource : IDisposable
{
private IntPtr _HandleToSomethingUnmanaged;
public ClassThatHoldsUnmanagedResource()
{
_HandleToSomethingUnmanaged = (... open file, whatever);
}
~ClassThatHoldsUnmanagedResource()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
(release unmanaged resource here);
... rest of dispose
}
public void Test()
{
IntPtr local = _HandleToSomethingUnmanaged;
... access resource through local here
}
在这一点上,如果Test在获取非托管句柄的副本后不使用任何实例数据,那会怎样呢?如果GC现在在我写下“DANGER”的地方运行,你看到这是怎么回事了吗?当GC运行时,它将执行finalizer,这将从仍在执行的Test中拔出对非托管资源的访问权限。
非托管资源通常通过IntPtr或类似方式访问,对于垃圾收集器来说是不透明的,并且在判断对象的生命周期时不考虑这些资源。
换句话说,我们在本地变量中保留句柄的引用对于GC来说毫无意义,它只会注意到没有实例引用剩余,因此认为对象是安全可收集的。
当然,这假设没有外部引用仍被认为是“活着”的对象。例如,如果上述类是从以下方法中使用的:
public void DoSomething()
{
ClassThatHoldsUnmanagedResource = new ClassThatHoldsUnmanagedResource();
ClassThatHoldsUnmanagedResource.Test();
}
那么你也会遇到完全相同的问题。
(当然,你可能不应该像这样使用它,因为它实现了IDisposable接口,你应该使用using
块或手动调用Dispose
方法。)
编写上述方法的正确方式是确保在我们仍需要对象时,GC不会将其收集:
public void Test()
{
IntPtr local = _HandleToSomethingUnmanaged;
... access resource through local here
GC.KeepAlive(this); // won't be collected before this has executed
}