我正在尝试编写用于测试内存泄漏的单元测试。 重现步骤:
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”
答案 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模式下工作。