使用WeakReference对内存泄漏进行单元测试时出现奇怪的行为

时间:2017-02-19 15:08:39

标签: c# .net memory-leaks garbage-collection

我正在尝试编写用于测试内存泄漏的单元测试。 重现步骤:

    TestClass test = new TestClass();
    WeakReference reference = new WeakReference(test, true);

    test = null;

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    Debug.Assert(reference.Target == null, "Memory Leak"); // This works

     //Replacing the above line with following, I see "Memory Leak" being printed.

    if (reference.Target != null)
    {
        Console.WriteLine("Memory Leak");
    }

我添加了终结器:

~TestClass() { Console.WriteLine("TestClass instance finalized");}

并且我注意到终结器在Assert情况下作为GC命令的一部分被调用但是当我用if条件替换它时,终结器不会作为GC命令的一部分被调用,因此引用的目标仍然存在。只有在程序存在之前才会调用终结器。

预期行为:

if(reference.Target != null) Console.WriteLine("Memory Leak");

应该有用。

实际行为:

Debug.Assert(reference.Target == null, "Memory Leak");

有效但

if(reference.Target != null) Console.WriteLine("Memory Leak");

无法正常打印“Memory Leak”

1 个答案:

答案 0 :(得分:2)

我找到了这个问题的根本原因。此代码将在Release构建配置中按预期工作,但不在调试中运行(这是我正在运行的)。

在Debug情况下,“test”不是GCed的原因是因为存在一个成员“reference”,它具有一个属性Target,它包含对“test”对象的引用。为了能够使用Watch窗口等调试工具查看此内容,编译器会将其保持活动状态。如果你摆脱了WeakReference实例(以及相应的if条件),即使在调试模式下你也会看到它被GC。此外,似乎如果在Debug.Assert中使用“引用”,它不包含对目标的引用,并使“test”成为GC。

在Release模式下,“test”之所以是GC的,是因为编译器JIT编译代码并摆脱了“test”变量(因为它总是分配给null)并且没有办法引用它在代码中的任何地方。这使它成为GC。由于“reference”是对“test”对象的弱引用,因此它不会保留它并允许它进行GC,因此if条件在Release模式下工作。