2011-09-27 22 views
7

.Net 4. ThreadLocal <> implementa IDisposable. Pero parece que llamar a Dispose() realmente no libera referencias para enhebrar objetos locales que se están reteniendo.ThreadLocal <> y fuga de memoria

Este código reproduce el problema:

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

rosca almacenes de datos locales una referencia a un objeto grande. GC no recoge estos objetos grandes si las referencias no se anulan manualmente. Usé el Administrador de tareas para observar el consumo de memoria. También ejecuto el generador de perfiles de memoria. Hice una instantánea después de recoger la basura. El generador de perfiles mostró que el objeto filtrado está arraigado por GCHandle y se asignó con aquí:

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 

Eso parece ser una falla en ThreadLocal <> diseño. El truco con el almacenamiento de todos los objetos asignados para una mayor limpieza es feo. ¿Alguna idea sobre cómo solucionar esto?

+1

¿Estás en depuración o la versión de este? Además, el administrador de tareas no es muy útil para lo que está midiendo –

+0

. Es mejor utilizar 'GC.GetTotalMemory (true)' para medir la memoria, pero eso tampoco garantiza que se recopile todo. – Ray

+1

GC.GetTotalMemory impreso(). Da 335607644 cuando no campo cero ** datos ** y 63268 cuando lo hago. – SergeyS

Respuesta

1

La memoria probablemente ha sido recogida de basura, pero el proceso CLR no se ha liberado aún. Tiende a retener la memoria asignada por un tiempo en caso de que la necesite más tarde, por lo que no tiene que hacer una asignación de memoria costosa.

+2

GC se comporta de manera diferente si el campo 'datos' se pone a cero. Plus Memory Profiler muestra que el objeto ThreadLocalData está realmente enraizado en alguna parte desde ThreadLocal <> internos. – SergeyS

1

Funcionando en .Net 4.5 DP, no veo ninguna diferencia entre presionar R o no en su aplicación. Si realmente hubo una pérdida de memoria en 4.0, parece que fue solucionado.

(4.5 es una actualización en el lugar, por lo que no puede probar 4.0 en el mismo equipo, lo siento.)