Чтобы освободить память за это отвечает сборщик мусора.
Основы работы с памятью в среде CLR:
• Каждый процесс имеет свое собственное отдельное виртуальное адресное пространство. Все процессы на одном компьютере совместно используют одну и ту же физическую память и один файл подкачки, если он есть.
• По умолчанию на 32-разрядных компьютерах каждому процессу выделяется 2 Гбайта виртуального адресного пространства в пользовательском режиме.
• Разработчики приложений работают только с виртуальным адресным пространством и никогда не управляют физической памятью напрямую. Сборщик мусора выделяет и освобождает виртуальную память для разработчика в управляемой куче.
При написании машинного кода для работы с виртуальным адресным пространством используются функции Win32. Эти функции выделяют и освобождают виртуальную память для разработчика в собственных кучах.
• Виртуальная память может находиться в трех состояниях.
Свободная - ссылки на блок памяти отсутствуют, и он доступен для выделения.
Зарезервировано - блок памяти доступен для использования разработчиком и не может использоваться для какого-либо другого запроса на выделение. Однако сохранение данных в этот блок памяти невозможно, пока он не будет выделен.
Выделена - блок памяти назначен физическому хранилищу.
• Виртуальное адресное пространство может стать фрагментированным. Это означает, что в адресном пространстве находятся свободные блоки, также известные как пропуски. Когда производится запрос на выделение виртуальной памяти, диспетчер виртуальной памяти должен найти один свободный блок достаточного размера для выполнения этого запроса на выделение. Даже если система имеет 2 Гбайт свободного пространства, операция выделения 2 Гбайт завершится неудачей, если это пространство не расположено в одном адресном блоке.
• Память может закончиться, если закончится виртуальное адресное пространство для резервирования или физическое пространство для выделения.
Файл подкачки используется, даже если нехватка физической памяти (то есть потребность в физической памяти) невелика. При первом возрастании нехватки физической памяти операционная система должна освободить пространство в физической памяти для хранения данных, и она производит резервное копирование некоторых данных, находящихся в физической памяти, в файл подкачки. Эти данные не выгружаются, пока в этом нет необходимости, так что с подкачкой можно столкнуться в ситуациях с очень небольшой нехваткой физической памяти.
Условия для сборки мусора
Коротко:
В случае нехватки в управляемой куче пространства для размещения запрашиваемого объекта начинает выполняться сборка мусора.
Подробнее:
Сборка мусора возникает при выполнении одного из следующих условий:
• Недостаточно физической памяти в системе. Это можно определить по уведомлению операционной системы о нехватке памяти или по сообщению узла о нехватке памяти.
• Память, используемая объектами, выделенными в управляемой куче, превышает допустимый порог. Этот порог непрерывно корректируется во время выполнения процесса.
• вызывается метод
GC.Collect. Практически во всех случаях вызов этого метода не потребуется, так как сборщик мусора работает непрерывно. Этот метод в основном используется для уникальных ситуаций и тестирования.
Управляемый heap
После инициализации средой CLR сборщик мусора выделяет сегмент памяти для хранения объектов и управления ими. Эта память называется управляемой кучей в отличие от собственной кучи операционной системы.
Управляемая куча создается для каждого управляемого процесса. Все потоки в процессе выделяют память для объектов в одной и той же куче.
Для резервирования памяти сборщик мусора вызывает функцию Win32 VirtualAlloc и резервирует для управляемых приложений по одному сегменту памяти за раз. Сборщик мусора также резервирует сегменты по мере необходимости и возвращает операционной системе освобожденные сегменты (очистив их от всех объектов), вызывая функцию Win32 VirtualFree.
Важно! Размер сегментов, выделенных сборщиком мусора, зависит от реализации и может быть изменен в любое время, в том числе при периодических обновлениях. Приложение не должно делать никаких допущений относительно размера определенного сегмента, полагаться на него или пытаться настроить объем памяти, доступный для выделения сегментов.
Чем меньше объектов распределено в куче, чем меньше придется работать сборщику мусора. При размещении объектов не используйте округленные значения, превышающие фактические потребности, например не выделяйте 32 байта, когда необходимо только 15 байтов.
Сборка мусора, когда она запущена, освобождает память, занятую неиспользуемыми объектами. Процесс освобождения сжимает используемые объекты, чтобы они перемещались вместе, и удаляет пространство, занятое неиспользуемыми объектами, уменьшая, таким образом, кучу. Это гарантирует, что объекты, распределенные совместно, останутся в управляемой куче рядом, чтобы сохранить их локальность.
Степень вмешательства (частота и длительность) сборок мусора зависит от числа распределений и сохранившейся в управляемой куче памяти.
Кучу можно рассматривать как совокупность двух куч: куча больших объектов и куча маленьких объектов.
Куча больших объектов содержит очень большие объекты размером от 85 000 байт. Объекты в куче больших объектов обычно являются массивами. Экземпляр объекта редко бывает очень большим.
Поколения
Куча организована в виде поколений, что позволяет ей обрабатывать долгоживущие и короткоживущие объекты. Сборка мусора в основном сводится к уничтожению короткоживущих объектов, которые обычно занимают только небольшую часть кучи. В куче существует три поколения объектов.
Поколение 0. Это самое молодое поколение содержит короткоживущие объекты. Примером коротко-живущего объекта является временная переменная. Сборка мусора чаще всего выполняется в этом поколении.
Вновь распределенные объекты образуют новое поколение объектов и неявно являются сборками поколения 0, если они не являются большими объектами, в противном случае они попадают в кучу больших объектов в сборке поколения 2.
Большинство объектов уничтожаются при сборке мусора для поколения 0 и не доживают до следующего поколения.
Поколение 1. Это поколение содержит коротко живущие объекты и служит буфером между коротко-живущими и долго-живущими объектами.
Поколение 2. Это поколение содержит долго-живущие объекты. Примером долго-живущих объектов служит объект в серверном приложении, содержащий статические данные, которые существуют в течение длительности процесса.
Сборки мусора выполняются для конкретных поколений при выполнении соответствующих условий. Сборка поколения означает сбор объектов в этом поколении и во всех соответствующих младших поколениях. Сборка мусора поколения 2 также называется полной сборкой мусора, так как она уничтожает все объекты во всех поколениях (то есть все объекты в управляемой куче).
При использовании
ссылочных типов, для них также будет отводиться место в стеке, только там будет храниться не значение, а адрес на участок памяти в heap. В
heap будут находиться сами значения данного объекта.
Если объект класса перестает использоваться, то при очистке стека ссылка на участок памяти также очищается, однако это не приводит к немедленной очистке самого участка памяти в куче.
Когда
сборщик мусора (garbage collector) увидит, что на данный участок памяти больше нет ссылок, он очистит память.
В методе
CreateCountry создается объект Country.
С помощью оператора
new в куче для хранения объекта
CLR выделяет участок памяти. А в стек добавляет адрес на этот участок памяти.
В главном методе
Main мы вызываем метод
CreateCountry. И после того, как CreateCountry отработает, место в стеке очищается, а сборщик мусора очищает ранее выделенный под хранение объекта country участок памяти.
Сборщик мусора не запускается сразу после удаления из стека ссылки на объект, размещенный в куче. Он запускается в то время, когда среда
CLR обнаружит в этом потребность, например, когда программе требуется дополнительная память.
Как правило, объекты в куче располагаются не друг за другом, между ними могут иметься пустоты. Куча довольно сильно фрагментирована. Поэтому после очистки памяти в результате очередной сборки мусора оставшиеся объекты перемещаются в один непрерывный блок памяти. Вместе с этим происходит обновление ссылок, чтобы они правильно указывали на новые адреса объектов.
Так же надо отметить, что
для крупных объектов существует своя куча - Large Object Heap. В эту кучу помещаются объекта, размер которых больше 85 000 байт. Особенность этой кучи состоит в том, что при сборке мусора сжатие памяти не проводится по причине больших издержек, связанных с размером объектов.
Несмотря на то что, на сжатие занятого пространства требуется время, да и приложение не сможет продолжать работу, пока не отработает сборщик мусора, однако благодаря подобному подходу также происходит оптимизация приложения. Теперь чтобы найти свободное место в куче среде
CLR не надо искать островки пустого пространства среди занятых блоков. Ей достаточно обратиться к указателю кучи, который указывает на свободный участок памяти, что уменьшает количество обращений к памяти.
Кроме того, чтобы снизить издержки от работы сборщика мусора, все объекты в куче разделяются по поколениям. Всего существует три поколения объектов: 0, 1, 2.
К поколению 0 относятся новые объекты, которые еще ни разу не подвергались сборке мусора. К поколению 1 относятся объекты, которые пережили одну сборку, а к поколению 2 - объекты, прошедшие более одной сборки мусора.
Когда сборщик мусора приступает к работе, он сначала анализирует объекты из поколению 0. Те объекты, которые остаются актуальными после очистки, повышаются до поколения 1.
Если после обработки объектов поколения 0 все еще необходима дополнительная память, то сборщик мусора приступает к объектам из поколения 1.
Те объекты, на которые уже нет ссылок, уничтожаются, а те, которые по-прежнему актуальны, повышаются до поколения 2.
Поскольку объекты из поколения 0 являются более молодыми и нередко находятся в адресном пространстве памяти рядом друг с другом, то их удаление проходит с наименьшими издержками.
Вывод: из-за поколений,
более новые объекты (вроде локальных переменных) будут
удаляться быстрее, а более старые (такие как объекты приложений) — реже.
С помощью перегруженных версий метода GC.Collect можно выполнить более точную настройку сборки мусора. Так, его перегруженная версия принимает в качестве параметра число - номер поколения, вплоть до которого надо выполнить очистку. Например, GC.Collect(0) - удаляются только объекты поколения 0.
Еще одна перегруженная версия принимает еще и второй параметр - перечисление GCCollectionMode.
Это перечисление может принимать три значения:
Default: значение по умолчанию для данного перечисления (Forced)
Forced: вызывает немедленное выполнение сборки мусора
Optimized: позволяет сборщику мусора определить, является ли текущий момент оптимальным для сборки мусора
Например, немедленная сборка мусора вплоть до первого поколения объектов:
GC.Collect(1, GCCollectionMode.Forced);