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)?
Il problema è causato dal effetto collaterale nel finalizzatore. Non farlo! – leppie
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
Interessante, ma il codice fallisce ancora se Console.WriteLine viene rimosso dal finalizzatore. –