Основы сборки мусора

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

В этой статье описаны основные понятия сборки мусора.

Преимущества

Использование сборщика мусора обеспечивает следующие преимущества:

  • Разработчикам не нужно освобождать память вручную.

  • Эффективно выделяет память для объектов в управляемой куче.

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

  • Обеспечивает безопасность памяти, убедившись, что объект не может использовать для себя память, выделенную для другого объекта.

Основы работы с памятью

В следующем списке перечислены важные понятия памяти СРЕДЫ CLR.

  • Каждый процесс имеет свое собственное отдельное виртуальное адресное пространство. Все процессы на одном компьютере используют одну и ту же физическую память и файл подкачки, если он есть.

  • По умолчанию на 32-разрядных компьютерах каждому процессу выделяется 2 Гбайт виртуального адресного пространства в пользовательском режиме.

  • Разработчики приложений работают только с виртуальным адресным пространством и никогда не управляют физической памятью напрямую. Сборщик мусора выделяет и освобождает виртуальную память для разработчика в управляемой куче.

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

  • Виртуальная память может находиться в трех состояниях.

    Область Описание
    Free Ссылки на блок памяти отсутствуют, и он доступен для выделения.
    Зарезервированное Блок памяти доступен для вашего использования и не может использоваться для любого другого запроса на выделение. Однако вы не сможете хранить данные в этом блоке памяти, пока он не будет зафиксирован.
    Фиксация Блок памяти назначен физическому хранилищу.
  • Виртуальное адресное пространство может быть фрагментировано, что означает, что в адресном пространстве есть свободные блоки, известные как дыры. При запросе выделения виртуальной памяти диспетчер виртуальной памяти должен найти один свободный блок, достаточно большой для удовлетворения запроса на выделение. Даже если в системе есть 2 ГБ свободного пространства, операция выделения 2 ГБ завершится неудачей, если это пространство не расположено в одном адресном блоке.

  • Память может закончиться, если будет недостаточно виртуального адресного пространства для резервирования или физического пространства для выделения.

    Файл подкачки используется, даже если нехватка физической памяти (потребность в физической памяти) низкая. При первой нехватке физической памяти операционная система должна освободить место в физической памяти для хранения данных, и она выполняет резервное копирование некоторых данных, которые хранятся в физической памяти, в файл подкачки. Данные не будут выстраивать на страницы, пока они не понадобятся, поэтому в ситуациях, когда нехватка физической памяти низка, можно столкнуться с разбиением по страницам.

Выделение памяти

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

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

Освобождение памяти

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

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

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

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

Условия для сборки мусора

Сборка мусора возникает при выполнении одного из следующих условий:

  • Недостаточно физической памяти в системе. Размер памяти определяется уведомлением о нехватке памяти от операционной системы или нехватке памяти, как указано узлом.

  • Объем памяти, используемой объектами, выделенными в управляемой куче, превышает допустимый порог. Этот порог непрерывно корректируется во время выполнения процесса.

  • вызывается метод GC.Collect . Почти во всех случаях не нужно вызывать этот метод, так как сборщик мусора работает непрерывно. Этот метод в основном используется для уникальных ситуаций и тестирования.

Управляемая куча

После инициализации сборщика мусора среда CLR выделяет сегмент памяти для хранения объектов и управления ими. Эта память называется управляемой кучей в отличие от собственной кучи операционной системы.

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

Для резервирования памяти сборщик мусора вызывает функцию Windows VirtualAlloc и резервирует для управляемых приложений по одному сегменту памяти за раз. Сборщик мусора также резервирует сегменты по мере необходимости и освобождает сегменты обратно в операционную систему (после очистки от любых объектов) путем вызова функции Windows VirtualFree .

Важно!

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

Чем меньше объектов распределено в куче, тем меньше придется работать сборщику мусора. При размещении объектов не используйте округленные значения, превышающие фактические потребности, например не выделяйте 32 байта, когда необходимо только 15 байтов.

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

Степень вмешательства (частота и длительность) сборок мусора зависит от числа распределений и сохранившейся в управляемой куче памяти.

Кучу можно рассматривать как совокупность двух куч: куча больших объектов и куча маленьких объектов. Куча больших объектов содержит объекты размером от 85 000 байтов, обычно представленные массивами. Редко объект экземпляра может быть очень большим.

Совет

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

Поколения

Алгоритм сборки мусора учитывает следующее:

  • Уплотнять память для части управляемой кучи быстрее, чем для всей кучи.
  • Новые объекты имеют более короткое время существования, а старые объекты — более длительные.
  • Новые объекты теснее связаны друг с другом, и приложение обращается к ним приблизительно в одно и то же время.

Сборка мусора в основном сводится к уничтожению короткоживущих объектов с небольшим временем жизни. Для оптимизации производительности сборщика мусора управляемая куча делится на три поколения: 0, 1 и 2. Следовательно, объекты с большим и небольшим временем жизни обрабатываются отдельно. Сборщик мусора хранит новые объекты в поколении 0. Уровень объектов, созданных на раннем этапе работы приложения и оставшихся после сборок мусора, повышается, и они сохраняются в поколении 1 и 2. Так как сжать часть управляемой кучи быстрее, чем всю кучу, эта схема позволяет сборщику мусора освобождать память в определенном поколении, а не для всей кучи при каждой сборке мусора.

  • Поколение 0: это поколение является самым молодым и содержит короткоживущие объекты. Примером короткоживущего объекта является временная переменная. Сборка мусора чаще всего выполняется в этом поколении.

    Вновь распределенные объекты образуют новое поколение объектов и неявно являются сборками поколения 0. Однако если это большие объекты, они переходят в кучу больших объектов (LOH), которую иногда называют поколением 3. Поколение 3 — это физическое поколение, которое логически собирается как часть поколения 2.

    Большинство объектов уничтожается при сборке мусора для поколения 0 и не доживает до следующего поколения.

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

  • Поколение 1. Это поколение содержит короткоживущие объекты и служит буфером между короткоживущие и долгоживущие объекты.

    Когда сборщик мусора выполняет сборку для поколения 0, память уплотняется для достижимых объектов и они продвигаются в поколение 1. Так как объекты, оставшиеся после сборки, обычно склонны к долгой жизни, имеет смысл продвинуть их в поколение более высокого уровня. Сборщику мусора необязательно выполнять повторную проверку объектов поколений 1 и 2 при каждой сборке мусора поколения 0.

    Если коллекция поколения 0 не освобождает достаточно памяти для приложения для создания нового объекта, сборщик мусора может выполнить сборку поколений 1, а затем 2. Объекты в поколении 1, оставшиеся после сборок, продвигаются в поколение 2.

  • Поколение 2. Это поколение содержит долгоживущие объекты. Примером долгоживущих объектов служит объект в серверном приложении, содержащий статические данные, которые существуют в течение длительности процесса.

    Объекты поколения 2, которые выживают в коллекции, остаются в поколении 2 до тех пор, пока они не будут определены как недоступные в будущей коллекции.

    Объекты в куче больших объектов (иногда называемой поколением 3) также собираются в поколении 2.

Сборки мусора происходят в определенных поколениях по мере того, как требуются условия. Сборка поколения означает сбор объектов в этом поколении и во всех соответствующих младших поколениях. Сборка мусора поколения 2 также называется полной сборкой мусора, так как она освобождает объекты во всех поколениях (т. е. все объекты в управляемой куче).

Выживание и переходы

Объекты, которые не были восстановлены в сборке мусора, называются выжившими и повышаются до следующего поколения:

  • Объекты, оставшиеся после сборки мусора поколения 0, подвигаются в поколение 1.
  • Объекты, оставшиеся после сборки мусора поколения 1, подвигаются в поколение 2.
  • Объекты, оставшиеся после сборки мусора поколения 2, остаются в поколении 2.

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

Эфемерные поколения и сегменты

Так как объекты в поколениях 0 и 1 являются короткоживущими, эти поколения называются эфемерными поколениями.

Эфемерные поколения выделяются в сегменте памяти, который называется эфемерным сегментом. Каждый новый сегмент, полученный сборщиком мусора, становится новым эфемерным сегментом и содержит объекты, пережившие сборку мусора для поколения 0. Старый эфемерный сегмент становится новым сегментом поколения 2.

Размер эфемерного сегмента зависит от того, является ли система 32-разрядной или 64-разрядной, а также от типа работающего сборщика мусора (рабочей станции или сборки мусора сервера). В следующей таблице показаны размеры эфемерного сегмента по умолчанию:

Сборка мусора рабочей станции и сервера 32-разрядная версия 64-разрядная версия
Сборщик мусора рабочей станции 16 МБ 256 МБ
Сборщик мусора сервера 64 МБ 4 Гбайт
Серверная сборка мусора с 4 логическими > ЦП 32 МБ 2 ГБ
Сборка мусора сервера с 8 логическими > ЦП 16 МБ 1 ГБ

Этот эфемерный сегмент может содержать объекты поколения 2. Объекты поколения 2 могут использовать несколько сегментов, сколько требуется вашему процессу и позволяет память.

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

Процесс сборки мусора

Сборка мусора состоит из следующих этапов:

  • Этап маркировки, выполняющий поиск всех используемых объектов и составляющий их перечень.

  • Этап перемещения, обновляющий ссылки на сжимаемые объекты.

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

    Так как сборки поколения 2 могут занимать несколько сегментов, объекты, перешедшие в поколение 2, могут быть перемещены в более старый сегмент. Как поколение 1, так и поколение 2 могут быть перенесены в другой сегмент, так как они повышены до поколения 2.

    Обычно куча больших объектов (LOH) не сжимается, так как копирование больших объектов налагает снижение производительности. Однако в .NET Core и в .NET Framework 4.5.1 и более поздних версиях можно использовать свойство GCSettings.LargeObjectHeapCompactionMode для сжатия большой кучи объектов по требованию. Кроме того, куча больших объектов автоматически сжимается при установке жесткого ограничения с помощью одного из следующих параметров:

    • Предельный объем памяти для контейнера.
    • Параметры конфигурации среды выполнения GCHeapHardLimit или GCHeapHardLimitPercent .

Чтобы определить, являются ли объекты используемыми, сборщик мусора задействует следующие сведения.

  • Корни стека. Переменные стека, предоставляемые JIT-компилятором и обходчиком стека. JIT-оптимизация позволяет уменьшить или увеличить области кода, в которых переменные стека сообщаются сборщику мусора.

  • Дескриптора сборки мусора. Обрабатывает, которые указывают на управляемые объекты и которые могут быть выделены пользовательским кодом или средой CLR.

  • Статические данные: статические объекты в доменах приложений, которые могут ссылаться на другие объекты. Каждый домен приложения следит за своими статическими объектами.

Перед запуском сборки мусора все управляемые потоки, кроме потока, запустившего сборку мусора, приостанавливаются.

На следующем рисунке показан поток, который запускает сборку мусора и приводит к приостановке других потоков:

Снимок экрана: активация сборки мусора потоком.

Неуправляемые ресурсы

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

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

Кроме того, нужно предусмотреть способ освобождения неуправляемых ресурсов в случае, если потребитель типа не вызовет Dispose. Вы можете использовать защищенный обработчик для создания оболочки для неуправляемого ресурса или переопределить метод Object.Finalize().

См. сведения об очистке неуправляемых ресурсов.

См. также