2011-09-27 21 views
7

.Net 4. ThreadLocal <>實現IDisposable。但似乎調用Dispose()實際上並不釋放對線程本地對象的引用。ThreadLocal <>和內存泄漏

此代碼重新產生問題:

using System; 
using System.Collections.Generic; 
using System.Collections.Concurrent; 
using System.Linq; 
using System.Threading; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     class ThreadLocalData 
     { 
      // Allocate object in LOH 
      public int[] data = new int[10 * 1024 * 1024]; 
     }; 

     static void Main(string[] args) 
     { 
      // Stores references to all thread local object that have been created 
      var threadLocalInstances = new List<ThreadLocalData>(); 
      ThreadLocal<ThreadLocalData> threadLocal = new ThreadLocal<ThreadLocalData>(() => 
      { 
       var ret = new ThreadLocalData(); 
       lock (threadLocalInstances) 
        threadLocalInstances.Add(ret); 
       return ret; 
      }); 
      // Do some multithreaded stuff 
      int sum = Enumerable.Range(0, 100).AsParallel().Select(
       i => threadLocal.Value.data.Sum() + i).Sum(); 
      Console.WriteLine("Sum: {0}", sum); 
      Console.WriteLine("Thread local instances: {0}", threadLocalInstances.Count); 

      // Do our best to release ThreadLocal<> object 
      threadLocal.Dispose(); 
      threadLocal = null; 

      Console.Write("Press R to release memory blocks manually or another key to proceed: "); 
      if (char.ToUpper(Console.ReadKey().KeyChar) == 'R') 
      { 
       foreach (var i in threadLocalInstances) 
        i.data = null; 
      } 
      // Make sure we don't keep the references to LOH objects 
      threadLocalInstances = null; 
      Console.WriteLine(); 

      // Collect the garbage 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
      GC.Collect(); 

      Console.WriteLine("Garbage collected. Open Task Manager to see memory consumption."); 
      Console.Write("Press any key to exit."); 
      Console.ReadKey(); 
     } 
    } 
} 

線程本地數據存儲到一個大的對象的引用。如果不手動刪除引用,則GC不會收集這些大對象。我使用任務管理器觀察內存消耗。我也運行內存分析器。收集垃圾後我做了一個快照。探查表明,泄漏的對象是由根深蒂固的GCHandle,並在這裏被分配:

mscorlib!System.Threading.ThreadLocal<T>.GenericHolder<U,V,W>.get_Boxed() 
mscorlib!System.Threading.ThreadLocal<T>.get_Value() 
ConsoleApplication2!ConsoleApplication2.Program.<>c__DisplayClass3.<Main>b__2(int) Program.cs 

這似乎是在ThreadLocal中<瑕疵>設計。存儲所有分配的對象進行進一步清理的技巧很難看。有關如何解決該問題的任何想法?

+1

你在調試或發佈此?另外,任務管理器對於你正在測量的內容並不是非常有用 –

+0

你最好使用'GC.GetTotalMemory(true)'來測量內存,但是這也不能保證所有內容都會被收集。 – Ray

+1

打印GC.GetTotalMemory()。當我不把零數據**字段和63268數字零字段給出時,它會給出335607644。 – SergeyS

回答

1

內存可能已被垃圾收集,但CLR過程還沒有放棄它。它往往會在稍後需要分配內存的情況下持續一段時間,所以它不需要執行昂貴的內存分配。

+2

如果「數據」字段歸零,則GC的行爲會有所不同。 Plus Memory Profiler顯示ThreadLocalData對象實際上是從ThreadLocal <>內部的某個地方進行了根。 – SergeyS

1

在.Net 4.5 DP上運行,我沒有看到按下R或不在您的應用程序之間有任何區別。如果在4.0中實際存在內存泄漏,它似乎已被修復。

(4.5是一個就地更新,所以我不能在同一臺計算機上測試4.0,對不起。)