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

Ste*_*ven 6 .net c# garbage-collection memory-leaks .net-4.5

我刚刚遇到了垃圾收集者的这种奇怪的"行为" 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

为什么ThreadLocal在这方面表现不同,如果在循环图中没有正确处理,会导致内存泄漏?这是设计的吗?如果是这样,这会记录在哪里?或者这是CLR的GC中的错误?

(在.NET 4.5下测试).

Ste*_*ven 0

微软的David Kean 证实这实际上是一个错误。