Работа со сборщиком мусора

176

До сих пор мы рассматривали приложение, как пассивный элемент, в отношении сборщика мусора. Мы изучали реализацию сборщика мусора и знакомились с важнейшими оптимизациями, выполняемыми автоматически и не требующими практически никаких действий с нашей стороны. В этом разделе мы исследуем доступные инструменты активного воздействия на сборщика мусора, с целью повысить производительность наших приложений и получить диагностическую информацию, недоступную иными способами.

Класс System.GC

Класс System.GC - это основная точка взаимодействий со сборщиком мусора .NET из управляемого кода. Он содержит множество методов, управляющих поведением сборщика мусора и возвращающих диагностическую информацию о нем.

Диагностические методы

Диагностические методы класса System.GC возвращают информацию о состоянии сборщика мусора. Предполагается, что эти методы будут использоваться для нужд диагностики проблем и отладки; не используйте их для принятия решений при работе приложения в обычном режиме. Информация, возвращаемая этими методами, доступна также в виде счетчиков производительности из категории .NET CLR Memory.

Метод GC.CollectionCount()

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

Метод GC.GetTotalMemory()

Возвращает объем занятой динамической памяти в байтах. Если передать методу аргумент со значением true, предварительно будет выполнена полная сборка мусора, чтобы возвращаемое значение точно соответствовало объему памяти, которая не может быть освобождена.

Метод GC.GetGeneration()

Возвращает номер поколения, которому принадлежит указанный объект. Обратите внимание, что в текущей реализации CLR объекты не всегда перемещаются между поколениями при сборке мусора.

Уведомления

Появившийся в версии .NET 3.5 SP1 прикладной интерфейс уведомлений сборщика мусора дает приложениям возможность заранее узнать о надвигающейся полной сборке мусора. Этот прикладной интерфейс доступен только при использовании непараллельного сборщика мусора и предназначен для использования в приложениях, в которых наблюдаются длительные паузы, вызванные сборкой мусора, и где требуется реализовать распределение работы или рассылку уведомлений, когда ожидается наступление очередной паузы.

Сначала приложение, заинтересованное в получении уведомлений от сборщика мусора, вызывает метод GC.RegisterForFullGCNotification() и передает ему два пороговых значения (числа от 1 до 99). Эти значения указывают, как рано приложение должно извещаться о наступлении сборки мусора, и основаны на пороговых значениях поколения 2 и кучи больших объектов. Проще говоря, чем больше значение, тем больше временной разрыв между уведомлением и фактической сборкой мусора. При маленьких значениях появляется риск не получить уведомление из-за того, что разрыв времени окажется слишком маленьким и механизм уведомлений просто не успеет сработать.

Затем приложение вызывает метод GC.WaitForFullGCApproach(), который блокируется до появления уведомления, выполняет подготовительные операции перед сборкой мусора и вызывает метод GC.WaitForFullGCComplete(), который блокируется до окончания сборки мусора. Поскольку все методы выполняются синхронно, есть смысл вызывать их в фоновом потоке и возбуждать событие в основном потоке выполнения программы, как показано ниже:

public class GCWatcher
{
    private Thread watcherThread;

    public event EventHandler GCApproaches;
    public event EventHandler GCComplete;

    public void Watch()
    {
        GC.RegisterForFullGCNotification(50, 50);
        watcherThread = new Thread(() =>
        {
            while (true)
            {
                GCNotificationStatus status = GC.WaitForFullGCApproach();

                // Код обработки ошибок опущен
                if (GCApproaches != null)
                {
                    GCApproaches(this, EventArgs.Empty);
                }

                status = GC.WaitForFullGCComplete();

                // Код обработки ошибок опущен
                if (GCComplete != null)
                {
                    GCComplete(this, EventArgs.Empty);
                }
            }
        });

        watcherThread.IsBackground = true;
        watcherThread.Start();
    }

    public void Cancel()
    {
        GC.CancelFullGCNotification();
        watcherThread.Join();
    }
}

3a дополнительной информацией и подробными примерами использования прикладного интерфейса уведомлений для распределения нагрузки на сервер обращайтесь к документации на сайте MSDN: "Уведомления о сборке мусора".

Управляющие методы

Метод GC.Collect() предписывает сборщику мусора выполнить сборку в указанном поколении (включая все более молодые поколения). Начиная с версии .NET 3.5 (а также в версиях .NET 2.0 SP1 и .NET 3.0 SP1), метод GC.Collect() был перегружен параметром типа перечисления GCCollectionMode. Это перечисление определяет следующие значения:

GCCollectionMode.Forced

Вынуждает сборщика мусора немедленно выполнить сборку, синхронно с текущим потоком выполнения. Метод возвращает управление только по завершении сборки мусора.

GCCollectionMode.Optimized

Позволяет сборщику мусора самому решить, насколько оправданно будет выполнить сборку в данный момент. Сборщик мусора может решить отложить сборку. Это рекомендуемый режим, если вашей целью является помочь сборщику мусора, подсказывая, когда сборка мусора является желательной. Для диагностики или когда требуется принудительно выполнить полную сборку мусора, чтобы удалить определенный объект, используйте значение GCCollectionMode.Forced.

GCCollectionMode.Default

Начиная с версии CLR 4.5 значение GCCollectionMode.Default действует эквивалентно значению GCCollectionMode.Forced.

Принудительная сборка мусора редко применяется на практике. Оптимизации, описанные в предыдущих статьях, в значительной степени опираются на динамические настройки и эвристики, хорошо протестированные в приложениях самых разных типов. Мы не рекомендуем вызывать принудительную сборку мусора и даже советовать сборщику мусора начать ее (передачей значения GCCollectionMode.Optimized) без явной необходимости. А теперь, после всего сказанного выше, можно обозначить ситуации, когда можно подумать об использовании возможности выполнять принудительную сборку мусора:

В версии .NET 4.5 появилась еще одна перегруженная версия метода GC.Collect() с дополнительным логическим параметром: GC.Collect(int generation, GCCollectionMode mode, bool blocking). Этот параметр управляет необходимостью блокировки на время выполнения сборки мусора (по умолчанию включена, в противном случае сборка выполняется асинхронно, в фоновом потоке).

Ниже перечислены другие методы управления сборщиком мусора:

Методы GC.AddMemoryPressure() и GC.RemoveMemoryPressure()

Их можно использовать для уведомления сборщика мусора о выделении или освобождении неуправляемой памяти в текущем процессе. Уведомление о выделении памяти сообщает сборщику мусора, какой объем неуправляемой памяти был выделен процессу. Сборщик мусора может использовать эту информацию для настройки агрессивности и частоты сборки мусора или игнорировать ее. При освобождении неуправляемой памяти можно уведомить сборщик мусора о снятии лишнего давления на память.

GC.WaitForPendingFinalizers()

Этот метод блокирует выполнение текущего потока, пока не завершатся все методы-финализаторы. Этот метод следует использовать с большой осторожностью, потому что он может приводить к взаимоблокировкам. Например, если главный поток выполнения окажется заблокирован в вызове GC.WaitForPendingFinalizers() в тот момент, когда он удерживает некоторую блокировку, и один из активных финализаторов попытается приобрести ту же самую блокировку, возникнет состояние взаимоблокировки. Так как метод GC.WaitForPendingFinalizers() не имеет параметра, ограничивающего предельное время ожидания, финализаторы, пытающиеся приобрести блокировку, должны использовать таймаут для обработки ошибочных ситуаций.

Методы GC.SuppressFinalize() и GC.ReRegisterForFinalize()

Используются совместно с механизмами финализации и воскрешения объектов.

Начиная с версии .NET 3.5 (доступен также в версиях .NET 2.0 SP1 и .NET 3.0 SP1) появился еще один интерфейс к сборщику мусора в виде класса GCSettings, который позволяет управлять поведением сборщика мусора и переключаться в режим с низкой задержкой. Описание других методов и свойств класса System.GC, не упомянутых в этой статье, ищите в документации на сайте MSDN.

Взаимодействие с применением интерфейсов размещения CLR

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

Эти проблемы невозможно решить с применением только управляемого кода. Поэтому, для реализации более полного управления сборщиком мусора необходимо выполнить размещение CLR. Этот прием обеспечивает множество механизмов управления памятью в .NET.

Ниже приводится небольшой фрагмент кода из открытого проекта Non-Paged CLR Host, демонстрирующий, как можно выполнить настройку выделения сегментов по запросам размещаемой среды выполнения CLR и как обеспечить выделение страниц памяти, не вытесняемых в файл подкачки:

Фрагмент кода из библиотеки Non-Paged CLR Host

Триггеры сборщика мусора

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

  1. Заполнение области памяти для поколения 0. Это происходит всякий раз, когда приложение создает новый объект в небольшой области памяти.

  2. Заполнение кучи больших объектов достигло порогового значения. Это происходит при создании больших объектов.

  3. Явный вызов метода GC.Collect().

  4. Операционная система сообщила о нехватке памяти. Для слежения за утилизацией памяти и соблюдения типичных требований, предъявляемых операционной системой, среда CLR использует Win32 API-уведомления.

  5. Выгрузка экземпляра AppDomain.

  6. Завершение процесса (или CLR). В этом случае производится вырожденная сборка мусора - ничто не интерпретируется как корень, объекты не переносятся в старшие поколения и сжатие динамической памяти не производится. Основная цель этого цикла сборки мусора - выполнить методы-финализаторы.

Пройди тесты
Лучший чат для C# программистов