垃圾回收应该已删除的对象,但仍WeakReference.IsAlive返回true(Garbage

2019-07-20 23:18发布

我有一个测试,我期待通过,但垃圾收集器的行为是不是因为我认为:

[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
}

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

看来,因为obj变量是在同一范围内声明的GC.Collect它不被收集。 如果我移动obj的声明和初始化之外的方法测试通过。

有没有人有这种行为的任何技术参考文档或解释?

Answer 1:

打同样的问题,因为你 - 我的测试是路过无处不在,除了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();
}


Answer 2:

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

  • 我不知道在需要局部变量的寿命是有限的C#规格东西。 在非调试版本,我觉得编译器可以自由地忽略最后分配到obj (将其设置为null ),因为没有代码路径会导致价值obj绝不会后可以使用,但我希望在非调试建立元数据将指示变量从未创建弱引用的后使用。 在调试版本,变量应在整个功能范围存在,但obj = null; 声明实际上应该清除它。 不过,我不能确定的是,C#规范承诺,编译器将不能省略最后一条语句,但仍然保持变量左右。

  • 如果您使用的是并发垃圾收集器,这将可能是GC.Collect()触发采集的立即开始,但该集合实际上不会前完成GC.Collect()的回报。 在这种情况下,它可能没有必要等待所有终结运行,从而GC.WaitForPendingFinalizers()可能是矫枉过正,但它可能会解决这个问题。

  • 当使用标准垃圾收集,我不会想到一个弱引用的存在为对象,以延长的方式对象的存在是一个终结会,但使用并发垃圾收集器时,它可能是被遗弃的对象,其弱引用存在获得移动到与需要被清理弱引用对象的队列中,而这种清理的处理上,与所有其他同时运行一个单独的线程发生。 在这种情况下,将呼叫GC.WaitForPendingFinalizers()将是必要的,以实现所期望的行为。

需要注意的是一个一般不应指望弱引用将与及时性的任何特定程度的无效,也不应该期望获取TargetIsAlive报道属实将产生一个非空引用。 每个人都应该使用IsAlive仅在一个不会在乎目标是否还活着的情况下,但有兴趣知道,参考已经死亡。 例如,如果一个人的集合WeakReference对象,不妨定期遍历列表,并删除WeakReference对象,其目标已经死亡。 每个人都应该为的可能性做好准备, WeakReferences可能会保留在集合中长于将是非常必要的; 如果他们这样做的唯一后果应该是内存和CPU时间稍有浪费。



Answer 3:

据我所知,呼吁Collect并不能保证所有的资源被释放。 你只是做一个建议,垃圾收集器。

你可以尝试迫使它阻止,直到所有对象都通过这样释放:

GC.Collect(2, GCCollectionMode.Forced, true);

我预计,这可能不工作的时候绝对100%。 在一般情况下,我会避免写依赖于观察垃圾收集器的任何代码,它是不是真的这样设计使用。



Answer 4:

难道说的.Should()扩展方法以某种方式挂在一参考? 或许测试框架的一些其他方面引起此问题。

(我张贴此作为一个答案,否则我不能轻易地张贴代码!)

我曾尝试下面的代码,它工作正常(的Visual Studio 2012,.NET 4的建造,调试和发布,32位和64位,在Windows 7上,四核处理器上运行):

using System;

namespace Demo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var obj = new object();
            var wRef = new WeakReference(obj);

            GC.Collect();
            obj = null;
            GC.Collect();

            Console.WriteLine(wRef.IsAlive); // Prints false.
            Console.ReadKey();
        }
    }
}

当您尝试这种代码,会发生什么?



Answer 5:

我有你需要调用的感觉GC.WaitForPendingFinalizers() ,因为我预计本周引用被终结线程更新。

写作时一个单元测试和记得,我在很多年前就有问题与WaitForPendingFinalizers()帮助,所以也使得以调用GC.Collect()

该软件在现实生活中绝不外泄,但编写单元测试,以证明该物体是不是保持活着是一个很多困难,然后我希望。 (我们与缓存确实让它活着过去有错误。)



文章来源: Garbage Collection should have removed object but WeakReference.IsAlive still returning true