垃圾收集应该已经删除了对象,但WeakReference.IsAlive仍然返回true

Tec*_*one 13 .net c# garbage-collection weak-references

我有一个我希望通过的测试,但垃圾收集器的行为并不像我假设的那样:

[Test]
public void WeakReferenceTest2()
{
    var obj = new object();
    var wRef = new WeakReference(obj);

    wRef.IsAlive.Should().BeTrue(); //passes

    GC.Collect();

    wRef.IsAlive.Should().BeTrue(); //passes

    obj = null;

    GC.Collect();

    wRef.IsAlive.Should().BeFalse(); //fails
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,obj对象应该是GC'd,因此我希望该WeakReference.IsAlive属性返回false.

似乎因为obj变量声明的范围与GC.Collect未收集的变量相同.如果我在方法之外移动obj声明和初始化,则测试通过.

有没有人对此行为有任何技术参考文档或解释?

小智 11

遇到与你相同的问题 - 我的测试到处传递,除了NCrunch(在你的情况下可能是任何其他仪器).嗯.使用SOS进行调试后,在测试方法的调用堆栈上显示了其他根.我的猜测是,它们是代码检测的结果,禁用了任何编译器优化,包括那些正确计算对象可达性的编译器优化.

这里的解决方法非常简单 - 不要从GC和测试活力的方法中获得强有力的参考.这可以通过简单的辅助方法轻松实现.下面的更改使您的测试用例通过了NCrunch,它最初失败了.

[TestMethod]
public void WeakReferenceTest2()
{
    var wRef2 = CallInItsOwnScope(() =>
    {
        var obj = new object();
        var wRef = new WeakReference(obj);

        wRef.IsAlive.Should().BeTrue(); //passes

        GC.Collect();

        wRef.IsAlive.Should().BeTrue(); //passes
        return wRef;
    });

    GC.Collect();

    wRef2.IsAlive.Should().BeFalse(); //used to fail, now passes
}

private T CallInItsOwnScope<T>(Func<T> getter)
{
    return getter();
}
Run Code Online (Sandbox Code Playgroud)


sup*_*cat 10

我可以看到一些潜在的问题:

  • 我不知道C#规范中的任何内容,它要求局部变量的生命周期是有限的.在非调试版本中,我认为编译器可以自由地省略最后一个赋值obj(设置为null),因为没有代码路径会导致obj在它之后永远不会使用的值,但我希望在非debug build元数据将指示在创建弱引用之后永远不会使用该变量.在调试版本中,变量应该存在于整个函数范围内,但该obj = null;语句应该实际清除它.尽管如此,我不确定C#规范承诺编译器不会省略最后一个语句,但仍保留变量.

  • 如果您正在使用并发垃圾收集器,则可能会GC.Collect()触发集合的立即启动,但该集合在GC.Collect()返回之前实际上不会完成.在这种情况下,可能没有必要等待所有终结器运行,因此GC.WaitForPendingFinalizers()可能过度,但它可能会解决问题.

  • 当使用标准垃圾收集器时,我不希望存在对象的弱引用以终结器的方式延长对象的存在,但是当使用并发垃圾收集器时,它可能被放弃的对象存在弱引用被移动到具有需要清理的弱引用的对象队列,并且这种清理的处理发生在与其他所有内容同时运行的单独线程上.在这种情况下,需要调用GC.WaitForPendingFinalizers()才能实现所需的行为.

请注意,通常不应期望弱引用将以任何特定程度的及时性无效,也不应期望TargetIsAlive报告为true 之后获取将引发非空引用.人们应该IsAlive只在一个人不关心目标的情况下使用,如果目标仍然存在,但是有兴趣知道该参考已经死亡.例如,如果有一个WeakReference对象集合,则可能希望周期性地遍历列表并删除WeakReference目标已经死亡的对象.人们应该为WeakReferences可能留在收集中的时间长于理想必要的时间做好准备; 如果他们这样做的唯一后果应该是轻微浪费内存和CPU时间.