循环图中使用 ThreadLocal<T> 时内存泄漏

Memory leak when ThreadLocal<T> is used in cyclic graph

我刚刚遇到垃圾收集器的这个奇怪的 'behavior' 关于 System.Threading.ThreadLocal<T> 我无法解释。在正常情况下,ThreadLocal<T> 实例在超出范围时将被垃圾回收,即使它们没有正确处理,除非它们是循环对象图的一部分。

下面的例子演示了这个问题:

public class Program
{
    public class B { public A A; }
    public class A { public ThreadLocal<B> LocalB; }

    private static List<WeakReference> references = new List<WeakReference>();

    static void Main(string[] args) {
        for (var i = 0; i < 1000; i++)
            CreateGraph();

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

        // Expecting to print 0, but it prints 1000
        Console.WriteLine(references.Count(c => c.IsAlive));
    }

    static void CreateGraph() {
        var a = new A { LocalB = new ThreadLocal<B>() };
        a.LocalB.Value = new B { A = a };
        references.Add(new WeakReference(a));

        // If either one of the following lines is uncommented, the cyclic
        // graph is broken, and the programs output will become 0.
        // a.LocalB = null;
        // a.LocalB.Value = null;
        // a.LocalB.Value.A = null;
        // a.LocalB.Dispose();
    }
}

虽然不调用 Dispose 不是好的做法,但 CLR 的设计是最终清理资源(通过调用终结器),即使 Dispose 没有被调用。

为什么 ThreadLocal 在这方面表现不同,并且在循环图的情况下如果处理不当会导致内存泄漏?这是设计使然吗?如果是这样,这在哪里记录?或者这是 CLR 的 GC 中的错误?

(在 .NET 4.5 下测试)。

原因是您没有调用 Dispose。垃圾收集器将只清理具有终结器的对象作为最后的手段。

Microsoft David Kean confirmed 认为这实际上是一个错误。