Почему GC не запускается, когда очередь финализации использует много памяти?

Я начинаю изучать 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 автоматически? В примере с включенными финализаторами будет ли сборщик мусора запускаться снова после первого раза?


person Aleph    schedule 18.11.2019    source источник
comment
GC срабатывает, если есть необходимость сделать это. Вы не можете определить, когда это произойдет.   -  person HimBromBeere    schedule 18.11.2019
comment
В зависимости от вашей машины 1 ГБ может быть не так много.   -  person TaW    schedule 18.11.2019
comment
Добавление объекта в очередь финализации означает, что объект сохраняется и перемещается в следующее следующее поколение (из поколения 0 в поколение 1).   -  person Pavel Anikhouski    schedule 18.11.2019
comment
Без финализатора приходилось запускать сборщик мусора, когда приложение достигало 90 МБ памяти, так почему же добавление финализатора позволяет приложению использовать более чем в 10 раз больше памяти, чем раньше было пороговым значением для сборщика мусора?   -  person Aleph    schedule 19.11.2019
comment
Целью GC не является экономия памяти. Цель состоит в том, чтобы предоставить вам память, если это необходимо. Таким образом, он будет работать, если вы запросите (выделите) большой объем памяти, а не если вы (имеете представление) освободить память. Но он также запускается, если приложение бездействует (скучно) и есть определенное количество объектов, помеченных для удаления. Это непредсказуемо.   -  person Holger    schedule 19.11.2019


Ответы (1)


Не полагайтесь на финализаторы. Они представляют собой подстраховку, до которой вам никогда не следует добираться, не первый вариант. Если финализаторам приходится убирать за вами, вы уже ужасно накосячили.

У меня есть два основных правила относительно одноразовых предметов, которые всегда работали:

  • Никогда не разделяйте создание и удаление экземпляра. Создавайте, используйте, распоряжайтесь. Все в одном фрагменте кода, в идеале с использованием с помощью блока.
  • Если вы не можете сделать первое, например, когда вы оборачиваете что-то, что реализует IDisposeable, ваш класс реализует IDisposeable с единственной целью ретрансляции вызова Dispose.

Что касается ГК:

Пока работает сборщик мусора, все остальные потоки должны быть приостановлены. Это абсолютное правило. В результате GC довольно ленив. Он пытается избежать бега. Действительно, если он запускается только один раз во время закрытия приложения, это идеальный случай.

person Christopher    schedule 18.11.2019