3

我正在编写垃圾收集测试,并偶然发现了在调试模式下发生的一组奇怪的错误。这是一个提炼的 POC 代码。有 20 个 lambda 的列表和对它们的弱引用列表。我访问第 13 个 lambda(调用 .ToString()),然后清除列表。然后强制垃圾收集并分析哪些元素在清理过程中幸存下来。

    public void TestGC_WTF() {
        var handlers = new List<Action>();
        var weakReferences = new List<WeakReference>();

        int testValue = 0;
        for (int i = 0; i < 20; i++) {
            int number = i;
            Action handler = () => testValue += number;
            handlers.Add(handler);
            weakReferences.Add(new WeakReference(handler));
            handler = null;
        }

        handlers[13].ToString();

        handlers.Clear();

        GC.Collect();

        if (false) { } //This is required for the bug to occur.

        var aliveReferences = Enumerable.Range(0, weakReferences.Count).Where(i => weakReferences[i].IsAlive).ToArray();
        Console.WriteLine("Uncollected handlers: {0}", string.Join(",", aliveReferences));
    }

在调试模式下,此代码将打印(“未收集的处理程序:13,19”。我对此结果的问题如下:

  1. 未收集第 19 个元素(即使我在每次循环迭代结束时将句柄变量显式设置为 null)
  2. 未收集第 13 个元素(即使我从未将其存储在任何地方)
  3. if (false) { }(或任何其他循环/条件)对于触发错误至关重要。

是什么导致出现问题?(我知道在调试中,对象被保留到定义它们的块的末尾。仅此一点仍然不能解释我遇到的任何问题。)

我已将错误提交给 Microsoft,但我对问题的原因更感兴趣https://connect.microsoft.com/VisualStudio/feedback/details/775082/strangely-triggered-strange-bugs-with-garbage -collection-in-debug-builds

4

2 回答 2

3

这是特定于 x86 抖动的。是的,这看起来像一个错误,抖动将堆栈帧上的临时存储位置标记为有效的对象引用。与 [ebp-74h] 一样,存储处理程序 [13] 引用的插槽和 [ebp-68h] 保留对处理程序 [19] 的引用。这些临时变量在 x86 代码中很常见,因为 cpu 寄存器的数量很少,而且当优化器被禁用时,抖动在寻找对少数寄存器的良好使用方面所花费的精力很少。

您可以在 connect.microsoft.com 提交反馈报告。然而,他们修复它的可能性非常小,当优化器被禁用时,抖动没有义务使其工作。局部变量的生命周期被延长到方法的末尾,以便于调试。对于声明的局部变量以及抖动分配的临时变量为真。当然,这最终无关紧要,您只会将发布版本发布给您的客户。当优化的发布版本也出现垃圾时,请随时提出这个问题,这不利于 GC 的有效性。

于 2012-12-24T18:58:36.307 回答
2

我不相信GC.Collect()一定会收集所有可能的垃圾,只需要付出努力。在调试版本中,优化有利于保持引用活动,因为您更有可能希望使用调试器检查它们。我在这里看不到任何我称之为错误的行为。

于 2012-12-24T16:41:48.543 回答