2011-09-27 13 views
7

.Net 4. ThreadLocal <> implementuje IDisposable. Wydaje się jednak, że wywołanie metody Dispose() w rzeczywistości nie zwalnia z odwoływania się do utrzymywanych lokalnych obiektów wątku.ThreadLocal <> i wyciek pamięci

Ten kod reprodukuje problem:

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(); 
     } 
    } 
} 

wątku lokalnych magazynów danych odniesienie do dużego obiektu. GC nie zbiera tych dużych obiektów, jeśli odniesienia nie są zerowane ręcznie. Użyłem Menedżera zadań do obserwowania zużycia pamięci. Prowadzę również profiler pamięci. Zrobiłem migawkę po zebraniu śmieci. Profiler wykazało, że wyciekły obiekt jest zakorzenione przez GCHandle i przeznaczono z tutaj:

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 

To wydaje się być wadą ThreadLocal <> design. Sztuczka z przechowywaniem wszystkich przydzielonych obiektów do dalszego oczyszczania jest brzydka. Wszelkie pomysły, jak to obejść?

+1

Czy jesteś w debugowania lub uwolnienia do tego? ponadto, menedżer zadań nie jest zbyt użyteczny dla tego, co mierzysz. –

+0

Lepiej używać "GC.GetTotalMemory (true)", aby zmierzyć pamięć, ale to też nie gwarantuje, że wszystko zostanie zebrane. – Ray

+1

Wydrukowano GC.GetTotalMemory(). Daje to 335607644, gdy nie zeruję ** pola danych ** i 63268, kiedy to robię. – SergeyS

Odpowiedz

1

Pamięć prawdopodobnie została zebrana, ale proces CLR jeszcze jej nie zwolnił. Ma tendencję do trzymania przydzielonej pamięci na bit, na wypadek, gdyby potrzebował jej później, więc nie musi wykonywać kosztownej alokacji pamięci.

+2

GC zachowuje się inaczej, jeśli pole "data" jest wyzerowane. Plus Profiler pamięci pokazuje, że obiekt ThreadLocalData jest w rzeczywistości zakorzeniony gdzieś z wewnętrznych elementów ThreadLocal <>. – SergeyS

1

Działa na .Net 4.5 DP, Nie widzę żadnej różnicy między naciśnięciem R lub nie w twojej aplikacji. Jeśli rzeczywiście wystąpił wyciek pamięci w wersji 4.0, wygląda na to, że został naprawiony.

(4,5 jest zmiana na miejscu, więc nie mogę przetestować 4.0 na tym samym komputerze, przepraszam.)