Я начинаю изучать GC и финализацию, и я наткнулся на довольно простой пример, когда поведение приложения для меня совершенно неожиданно.
(Примечание: я знаю, что финализаторы следует использовать только с неуправляемыми ресурсами и с использованием одноразового шаблона, я просто хочу понять, что здесь происходит.)
Это простое консольное приложение, которое генерирует «пилообразную» структуру памяти. Память увеличивается примерно до 90 МБ, а затем выполняет GC, падает и снова начинает увеличиваться, никогда не превышая 90 МБ.
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 100000; i++)
{
MemoryWaster mw = new MemoryWaster(i);
Thread.Sleep(250);
}
}
}
public class MemoryWaster
{
long l = 0;
long[] array = new long[1000000];
public MemoryWaster(long l)
{
this.l = l;
}
//~MemoryWaster()
//{
// Console.WriteLine("Finalizer called.");
//}
}
Если я удалю комментарий с помощью финализатора, поведение будет совсем другим — приложение выполняет один или два GC в начале, но затем память увеличивается линейным образом, пока не будет использовано более 1 ГБ памяти (в этот момент я завершаю приложение). )
Из того, что я прочитал, это связано с тем, что вместо выпуска элемента GC перемещает объект в очередь финализации. Сборщик мусора запускает поток для выполнения методов финализатора, а затем ожидает, пока другой сборщик мусора удалит завершенные объекты. Это может быть проблемой, когда методы финализатора работают очень долго, но здесь это не так.
Если я вручную запускаю run GC.Collect() каждые несколько итераций, приложение ведет себя так, как ожидалось, и я вижу пилообразный шаблон высвобождения памяти.
Мой вопрос: почему большой объем памяти, используемый приложением, не запускает GC автоматически? В примере с включенными финализаторами будет ли сборщик мусора запускаться снова после первого раза?