7

让我从一个演示开始:

[TestMethod]
public void Test()
{
    var h = new WeakReference(new object());
    GC.Collect();
    Assert.IsNull(h.Target);
}

此代码按预期工作。垃圾回收结束后,in 的引用h无效。现在,这里的转折:

[TestMethod]
public void Test()
{
    var h = new WeakReference(new object());
    GC.Collect();
    try { }      // I just add an empty
    finally { }  // try/finally block
    Assert.IsNull(h.Target); // FAIL!
}

我在该行之后向测试添加了一个空的 try/finally 块GC.Collect(),瞧,弱引用的对象没有被收集!如果在该行之前添加了空的 try/finally 块GC.Collect(),则测试通过。

是什么赋予了?谁能准确解释 try/finally 块如何影响对象的生命周期?

注意:所有测试都在 Debug 中完成。在 Release 中,两个测试都通过了。

注意 2:要重现该应用程序必须以 .NET 4 或 .NET 4.5 运行时为目标,并且必须以 32 位运行(目标 x86 或选中“首选 32 位”选项的任何 CPU)

4

2 回答 2

3

附加调试器时,抖动会改变局部变量的生命周期。在这个答案中详细解释。简而言之,如果没有调试器,生命周期将在代码中最后一次使用变量时结束,而使用调试器,它会扩展到方法的末尾,以允许 Watch 调试器表达式工作。

虽然看起来表达式没有存储在代码中的new object()变量中,但在抖动代码生成器完成后仍有一个变量。对象引用存储在 [ebp-44h] 的堆栈帧中,与使用局部变量的方式没有区别。您可以看到的唯一方法是查看生成的机器代码,使用 Debug + Windows + Disassembly。否则这是完全正常的,抖动优化器消除了这些冗余内存存储,但在调试版本中未启用。

即使它是临时的,这个变量仍然需要报告给 GC 作为存储引用。当在对象构造函数调用和 WeakReference 构造函数调用之间发生 GC 时,必须防止对象被收集。如果程序中的另一个线程触发集合,则可能。

如果没有 try/finally 块,jitter 仍然可以发现堆栈帧槽存储了一个临时的,实际上不需要延长它的生命周期。因此它在 GC.Collect() 调用和对象被收集之前停止报告临时对象的生命周期。

但是对于 try/finally 块,抖动会放弃尝试确定 try 或 finally 块中是否可能使用堆栈帧槽。并通过简单地将其生命周期延长到方法的末尾来解决问题,就像普通的局部变量一样。

这很正常,您根本无法对非优化代码中处理局部变量引用的方式做出任何合理的假设。对于实际使用在单元测试器中运行的 [TestMethod] 的任何人,这也应该是一个强烈的警告,永远不要测试代码的 Debug 构建,只测试 Release 构建。它的行为与它在用户机器上的工作方式不同。

于 2013-09-05T10:45:32.977 回答
0

为了更容易调试,在调试模式下本地声明的对象不会被释放。虽然我无法重现您的问题,但使用以下代码:

var x = new object();
var h = new WeakReference(x);
GC.Collect();
try { }      // I just add an empty
finally { }  // try/finally block
Console.WriteLine(h.Target != null);
Console.ReadKey();

我能够重现该问题。如果GC.Collect()能够“收集” new object(),如果您在 之后放置断点,Console.ReadKey()您将能够看到已处置的对象 ( x)。

有人在这里问过类似的问题:https ://stackoverflow.com/a/755688/613130

一个评论很有趣:

由于发布模式的优化,引用范围仅在其最后一次使用之前有效,而不是定义它的整个代码块。

显然在调试模式下正好相反,参考范围是定义它的整个范围。

于 2013-09-05T09:34:40.593 回答