2012-05-09 11 views
14

CodiceI delegati possono causare una perdita di memoria? GC.TotalMemory (vero) sembra indicare in modo

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

uscita

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

Grazie per la replica eseguibile, btw! – usr

risposta

17

caso interessante. Ecco la soluzione:

enter image description here

delegati alla combinazione si osservativamente pura: Sembra che i delegati sono immutabili verso l'esterno. Ma al internamente, i delegati esistenti sono stati modificati. Condividono, a determinate condizioni, lo stesso _invocationList per motivi di prestazioni (ottimizzando per lo scenario che alcuni delegati sono collegati allo stesso evento). Sfortunatamente, lo _invocationList per fa riferimento allo bigMgdResDelegate che fa sì che la memoria sia mantenuta attiva.

+1

Ottima risposta con immagini e tutto. –

+0

Wow, è sorprendente! Ma è una vera mutazione o un'intelligente ottimizzazione locale? Forse il compilatore/JIT guarda avanti nel metodo e crea un array di 4 elementi pre-popolato una volta prima di creare il primo delegato? – Weeble

+0

@Weble, non sono sicuro di cosa intendi per ottimizzazione JIT, ma il JIT di solito è piuttosto stupido. Non fa cose sofisticate come quella. Ad ogni modo, il JIT può solo cambiare le cose quando il codice IL lo ordina. * Non * introduce mai la mutazione stessa perché ciò non sarebbe sicuro. – usr

1

Potrei mancare il punto qui, ma la garbage collection è di progettazione non deterministica. Ergo, spetta al framework .NET decidere quando recupera la memoria.

È possibile eseguire GC.GetTotalMemory in un ciclo semplice e ottenere figure diverse. Forse nessuna sorpresa, poiché la documentazione specifica che la cifra restituita è un'approssimazione.

+0

D'accordo con questa risposta principalmente perché il test che l'OP propone è troppo semplice per rilevare molto di tutto ciò che è utile. La "perdita di memoria" negli ambienti gestiti si riduce di solito a un codice mal progettato (oggetti statici che contengono riferimenti a elenchi di cose). È altamente improbabile che in questo punto del gioco troverai dei bug reali nel GC. – Sprague

+0

GetTotalMemory esegue il GC esplicito. Questo test è abbastanza affidabile. Si noti inoltre che le variabili di annullamento delle voci funzionano in tutti gli altri casi in questo test (ulteriore evidenza che ciò funziona come previsto). – usr

+0

Penso che il punto dell'OP sia che 'GC.GetTotalMemory (true)' dovrebbe forzare una collezione, ma dopo aver annullato entrambi i delegati di grandi dimensioni e forzato una collezione, la memoria è ancora allocata. –

Problemi correlati