2013-02-06 14 views
9

Si consideri il codice qui sotto:Perché il garbage collector C# non continua a provare a liberare memoria fino a quando una richiesta può essere soddisfatta?

using System; 

namespace memoryEater 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      Console.WriteLine("alloc 1"); 
      var big1 = new BigObject(); 

      Console.WriteLine("alloc 2"); 
      var big2 = new BigObject(); 

      Console.WriteLine("null 1"); 
      big1 = null; 

      //GC.Collect(); 

      Console.WriteLine("alloc3"); 
      big1 = new BigObject(); 

      Console.WriteLine("done"); 
      Console.Read(); 
     } 
    } 

    public class BigObject 
    { 
     private const uint OneMeg = 1024 * 1024; 
     private static int _idCnt; 
     private readonly int _myId; 
     private byte[][] _bigArray; 

     public BigObject() 
     { 
      _myId = _idCnt++; 
      Console.WriteLine("BigObject {0} creating... ", _myId); 

      _bigArray = new byte[700][]; 

      for (int i = 0; i < 700; i++) 
      { 
       _bigArray[i] = new byte[OneMeg]; 
      } 

      for (int j = 0; j < 700; j++) 
      { 
       for (int i = 0; i < OneMeg; i++) 
       { 
        _bigArray[j][i] = (byte)i; 
       } 
      } 
      Console.WriteLine("done"); 
     } 

     ~BigObject() 
     { 
      Console.WriteLine("BigObject {0} finalised", _myId); 
     } 
    } 
} 

Ho una classe, BigObject, che crea una matrice 700MiB nel suo costruttore, e ha un metodo Finalize, che non fa altro che stampa alla console. In Main, creo due di questi oggetti, uno libero, e quindi ne creo un terzo.

Se questo è compilato per 32 bit (in modo da limitare la memoria a 2 GB), viene generata un'eccezione di memoria insufficiente durante la creazione del terzo BigObject. Questo perché, quando la memoria viene richiesta per la terza volta, la richiesta non può essere soddisfatta e quindi viene eseguito il garbage collector. Tuttavia, il primo BigObject, che è pronto per essere raccolto, ha un metodo finalizzatore, quindi anziché essere raccolto viene collocato nella coda di finalizzazione e viene finalizzato. Il garbage collecter si ferma e viene generata l'eccezione. Tuttavia, se la chiamata a GC.Collect non è commentata o il metodo finalize viene rimosso, il codice funzionerà correttamente.

La mia domanda è: perché il garbage collector non fa tutto il possibile per soddisfare la richiesta di memoria? Se funzionasse due volte (una volta per finalizzare e di nuovo per liberare) il codice sopra funzionerebbe benissimo. Il garbage collector non dovrebbe continuare a finalizzare e raccogliere fino a che non è possibile liberare più memoria prima di lanciare l'eccezione, e c'è un modo per configurarlo in questo modo (sia in codice che tramite Visual Studio)?

+1

Il problema è causato dal effetto collaterale nel finalizzatore. Non farlo! – leppie

+2

Mi sono imbattuto anche in questo alcuni anni fa e ho scritto un post sul blog. Vedi: http://xacc.wordpress.com/2011/02/22/gc-suppressfinalize/ (guarda anche i commenti) – leppie

+0

Interessante, ma il codice fallisce ancora se Console.WriteLine viene rimosso dal finalizzatore. –

risposta

0

Immagino sia perché il tempo che il finalizzatore esegue durante la garbage collection non è definito. Le risorse non sono garantite per essere rilasciate in qualsiasi momento specifico (a meno che non si chiami un metodo Close o un metodo Dispose.), Anche l'ordine di esecuzione dei finalizzatori è casuale in modo che si possa avere un finalizzatore su un altro oggetto in attesa, mentre l'oggetto attende che .

2

È indeterminato quando GC funzionerà e tenterà di recuperare memoria.

Se si aggiunge questa riga dopo big1 = null. Tuttavia dovresti preoccuparti di forzare GC a raccogliere. Non è raccomandato a meno che tu non sappia cosa stai facendo.

GC.Collect(); 
GC.WaitForPendingFinalizers(); 

Best Practice for Forcing Garbage Collection in C#

When should I use GC.SuppressFinalize()?

Garbage collection in .NET (generations)

+0

Capisco che questa è una cattiva pratica, è solo un'app demo per dimostrare il problema. La domanda è: perché il garbage collector non esegue i finalizzatori e la memoria libera (che consentirebbe l'allocazione) prima di arrivare a lanciare l'eccezione? –

+0

@SeanReid http://stackoverflow.com/questions/10016541/garbage-collection-not-happening-even-when-needed this. Dovrebbe essere ora ovvio il motivo per cui il GC non è mai successo automaticamente. Stai solo creando oggetti sul LOH (tieni presente che i tipi int, nel modo in cui li hai usati, sono allocati nello stack e non devono essere raccolti). Non riempi mai la generazione 0, quindi un GC non succede mai. – adt

+0

Il problema è che viene eseguito un GC e viene eseguito anche il finalizzatore dell'oggetto libero. (Si noti che l'applicazione è a 32 bit, in modo da assicurarsi di esaurire la memoria a 2 GB anziché avviare la pagina per la memoria). Il problema è che GC deve essere eseguito due volte, una volta per eseguire il finalizzatore e di nuovo per liberare effettivamente la memoria. –