2012-05-09 12 views
14

Código¿Pueden los delegados causar una pérdida de memoria? GC.TotalMemory (verdadero) por lo que parece indicar

using System; 
internal static class Test 
{ 
    private static void Main() 
    { 
     try 
     { 
      Console.WriteLine("{0,10}: Start point", GC.GetTotalMemory(true)); 
      Action simpleDelegate = SimpleDelegate; 
      Console.WriteLine("{0,10}: Simple delegate created", GC.GetTotalMemory(true)); 
      Action simpleCombinedDelegate = simpleDelegate + simpleDelegate + simpleDelegate; 
      Console.WriteLine("{0,10}: Simple combined delegate created", GC.GetTotalMemory(true)); 
      byte[] bigManagedResource = new byte[100000000]; 
      Console.WriteLine("{0,10}: Big managed resource created", GC.GetTotalMemory(true)); 
      Action bigManagedResourceDelegate = bigManagedResource.BigManagedResourceDelegate; 
      Console.WriteLine("{0,10}: Big managed resource delegate created", GC.GetTotalMemory(true)); 
      Action bigCombinedDelegate = simpleCombinedDelegate + bigManagedResourceDelegate; 
      Console.WriteLine("{0,10}: Big combined delegate created", GC.GetTotalMemory(true)); 
      GC.KeepAlive(bigManagedResource); 
      bigManagedResource = null; 
      GC.KeepAlive(bigManagedResourceDelegate); 
      bigManagedResourceDelegate = null; 
      GC.KeepAlive(bigCombinedDelegate); 
      bigCombinedDelegate = null; 
      Console.WriteLine("{0,10}: Big managed resource, big managed resource delegate and big combined delegate removed, but memory not freed", GC.GetTotalMemory(true)); 
      GC.KeepAlive(simpleCombinedDelegate); 
      simpleCombinedDelegate = null; 
      Console.WriteLine("{0,10}: Simple combined delegate removed, memory freed, at last", GC.GetTotalMemory(true)); 
      GC.KeepAlive(simpleDelegate); 
      simpleDelegate = null; 
      Console.WriteLine("{0,10}: Simple delegate removed", GC.GetTotalMemory(true)); 
     } 
     catch (Exception e) 
     { 
      Console.WriteLine(e); 
     } 
     Console.ReadKey(true); 
    } 
    private static void SimpleDelegate() { } 
    private static void BigManagedResourceDelegate(this byte[] array) { } 
} 

salida

GC.TotalMemory(true) 
    105776: Start point 
    191264: Simple delegate created 
    191328: Simple combined delegate created 
100191344: Big managed resource created 
100191780: Big managed resource delegate created 
100191812: Big combined delegate created 
100191780: Big managed resource, big managed resource delegate and big combined delegate removed, but memory not freed 
    191668: Simple combined delegate removed, memory freed, at last 
    191636: Simple delegate removed 
+0

Gracias por la reproducción ejecutable, por cierto! – usr

Respuesta

17

caso interesante. Aquí está la solución:

enter image description here

delegados que se combinan es observacionalmente pura: Parece que los delegados son inmutables al exterior. Pero internamente, los delegados existentes se están modificando. Comparten, bajo ciertas condiciones, el mismo _invocationList por razones de rendimiento (optimizando para el escenario que unos pocos delegados estén conectados al mismo evento). Desafortunadamente, el _invocationList para el simpleCombinedDelegate hace referencia al bigMgdResDelegate que hace que la memoria se mantenga activa.

+1

Excelente respuesta con imágenes y todo. –

+0

¡Guau, eso es sorprendente! Pero, ¿es una mutación genuina o una optimización local inteligente? ¿Quizás el compilador/JIT mira hacia delante en el método y crea una matriz precompuesta de 4 elementos una vez antes de crear el primer delegado? – Weeble

+0

@Weeble, no estoy seguro de lo que quiere decir con respecto a la optimización JIT, pero el JIT suele ser bastante tonto. No hace cosas sofisticadas como esa. De todos modos, el JIT solo puede mutar cosas cuando el código IL lo ordena. * Nunca * introduce la mutación en sí misma porque sería inseguro. – usr

1

Me puede estar faltando el punto aquí, pero la recolección de basura es por diseño no determinista. Ergo, depende del .NET Framework decidir cuándo reclama la memoria.

Puede ejecutar GC.GetTotalMemory en un bucle simple y obtener diferentes figuras. Tal vez no sea sorprendente ya que la documentación especifica que la cifra devuelta es una aproximación.

+0

Estoy de acuerdo con esta respuesta principalmente porque la prueba que propone el OP es demasiado simple para detectar gran parte de cualquier cosa útil. La 'memoria filtrada' en los entornos administrados generalmente se reduce a un código mal diseñado (objetos estáticos que contienen referencias a listas de cosas). Es muy poco probable que encuentres errores reales en el GC en este punto del juego. – Sprague

+0

GetTotalMemory hace GC explícita. Esta prueba es bastante confiable. También tenga en cuenta que las variables anuladas funcionan en todos los demás casos de esta prueba (más evidencia de que esto funciona como se esperaba). – usr

+0

Creo que el punto del OP es que se supone que 'GC.GetTotalMemory (true)' fuerza una colección, pero después de anular tanto a los delegados grandes como forzar una colección, la memoria aún se asigna. –

Cuestiones relacionadas