2012-05-09 8 views
14

-CodeKönnen Delegierte einen Speicherverlust verursachen? GC.TotalMemory (true) scheint so

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) { } 
} 

Ausgabe

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

Danke für die ausführbare Repro, BTW! – usr

Antwort

17

Interessante Fall anzuzeigen. Hier ist die Lösung:

enter image description here

Kombination Delegierten ist beobachtungs reine: Es sieht aus wie die Delegierten nach außen unveränderlich sind. Aber intern werden bestehende Delegaten geändert. Sie teilen unter bestimmten Bedingungen aus Leistungsgründen dasselbe _invocationList (Optimierung für das Szenario, dass einige Delegaten an dasselbe Ereignis angeschlossen sind). Leider verweist die _invocationList für die simpleCombinedDelegate die bigMgdResDelegate, die den Speicher am Leben erhält.

+1

Ausgezeichnete Antwort mit Bildern und alles. –

+0

Wow, das ist überraschend! Aber ist es echte Mutation oder eine clevere lokale Optimierung? Vielleicht sieht der Compiler/JIT in der Methode voraus und erstellt vor dem Erstellen des ersten Delegaten ein vorbestücktes 4-Elemente-Array? – Weeble

+0

@Weeble, nicht sicher, was Sie bezüglich JIT-Optimierung meinen, aber das JIT ist normalerweise ziemlich dumm. Es macht keine raffinierten Sachen wie diese. Wie auch immer, das JIT kann nur Zeug mutieren, wenn der IL-Code es anordnet. Es führt niemals selbst eine Mutation ein, weil das unsicher wäre. – usr

1

Ich könnte den Punkt hier fehlen, aber Garbage Collection ist von Design nicht deterministisch. Ergo, es ist Sache des .NET-Frameworks zu entscheiden, wann es Speicher zurückgewinnt.

Sie können GC.GetTotalMemory in einer einfachen Schleife ausführen und verschiedene Zahlen erhalten. Vielleicht keine Überraschung, da die Dokumentation angibt, dass die zurückgegebene Zahl eine Annäherung ist.

+0

Stimmen Sie dieser Antwort hauptsächlich zu, weil der Test, den das OP vorschlägt, zu einfach ist, um etwas Nützliches zu entdecken. "Durchgesickertes Gedächtnis" in verwalteten Umgebungen läuft normalerweise auf schlecht entworfenen Code hinaus (statische Objekte, die Verweise auf Listen von Dingen enthalten). Es ist sehr unwahrscheinlich, dass Sie zu diesem Zeitpunkt im Spiel irgendwelche echten Fehler im GC finden werden. – Sprague

+0

GetTotalMemory führt explizite GC aus. Dieser Test ist ziemlich zuverlässig. Beachten Sie auch, dass das Ausgleichen von Variablen in allen anderen Fällen in diesem Test funktioniert (weitere Hinweise, dass dies wie erwartet funktioniert). – usr

+0

Ich denke, der Punkt der OP ist, dass 'GC.GetTotalMemory (true)' eine Sammlung erzwingen soll, aber nach dem Nullen sowohl großer Delegaten als auch Erzwingen einer Sammlung wird der Speicher immer noch zugewiesen. –

Verwandte Themen