Разбираемся с Garbage Collection в C#

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

Garbage Collection

Специальный механизм, называемый сборщиком мусора (garbage collector), периодически освобождает память, удаляя объекты, которые уже не будут востребованы приложением- то есть производит «сбор мусора».

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

Любая программа использует ресурсы — файлы, буферы в памяти, пространство экрана, сетевые подключения, базы данных и т. п. В объектно-ориентированной среде каждый тип идентифицирует некий доступный этой программе ресурс. Чтобы им воспользоваться, должна быть выделена память для представления этого типа.

Для доступа к ресурсу вам нужно:

  • Выделить память для типа, представляющего ресурс
  • Инициализировать выделенную память, установив начальное состояние ресурса.
  • Использовать ресурс, обращаясь к членам его типа
  • Ликвидировать состояние ресурса
  • Освободить память

Сборка мусора (garbage collection) полностью освобождает разработчика от необходимости следить за использованием и своевременным освобождением памяти.

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

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

  • подсчитывает количество байтов, необходимых для размещения полей типа
  • прибавляет к полученному значению количество байтов, необходимых для размещения системных полей объекта.
  • Если в управляемой куче достаточно места для объекта, ему выделяется память, начиная с адреса, на который ссылается указатель NextObjPtr, а занимаемые им байты обнуляются.
  • Вызывается конструктор типа, и IL-команда newobj возвращает адрес объекта (также перемещается NextObjPtr)

При вызове оператора new в области, выделяемой под объект, может не хватать свободного адресного пространства. Куча выясняет объем недостающей памяти и добавляет байты, необходимые для объекта, к адресу, заданному указателем NextObjPtr . Если результирующее значение выходит за пределы адресного пространства, значит, куча заполнена и следует выполнить сборку мусора. Сборщик мусора проверяет наличие в куче больше не используемых приложением объектов, чтобы освободить занятую ими память. Сборщик переходит к этапу сборки мусора, называемому маркировкой (maгking). Он проходит по стеку потока и проверяет все корни маркируя объекты. После маркировки корня и объекта, сборщик мусора проверяет следующий корень и продолжает маркировать объекты. Встретив уже маркированный объект, сборщик мусора останавливается. Затем сборщик переходит к следующему этапу сборки мусора, называемому сжатием. Теперь он проходит кучу линейно в поисках непрерывных блоков не маркированных объектов, то есть мусора. Небольшие блоки сборщик не трогает, а в больших непрерывных блоках он перемешает вниз все не мусорные объекты, сжимая при этом кучу.
Куча организована в виде поколений, что позволяет ей обрабатывать долгоживущие и короткоживущие объекты. Сборка мусора в основном сводится к уничтожению короткоживущих объектов, которые обычно занимают только небольшую часть кучи. В куче существует три поколения объектов.

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

System.GC — класс для управления сбором мусора. Позволяет получать информацию о поколениях, а также принудительно инициировать сбор мусора. Кроме того, позволяет отменять
работу деструктора объекта.

System.Object.Finalize()  Финализацией (finalization) называется поддерживаемый CLR механизм, позволяющий объекту выполнить корректную очистку, прежде чем сборщик мусора освободит занятую им память. Любой тип, выполняющий функцию оболочки машинного ресурса, например файла, сетевого соединения, сокета, мьютекса и др., должен поддерживать финализацию. Для этого в типе реализуют метод финализации. Определив, что объект стал мусором, сборщик вызывает метод финализации объекта (если он есть).

Метод System.Object.Finalize() будет вызван сборщиком мусора непосредственно перед уничтожением объекта, его невозможно вызвать самостоятельно. Рекомендуется для очистки неуправляемых ресурсов. Метод Finalize представляет собой переопределение виртуального метода из System.Object, однако в С# не разрешается явно переопределять этот метод. Вместо этого должен быть написан деструктор, который выглядит как метод без типа возврата, без модификаторов, без параметров, идентификатором которого служит имя класса с предшествующим символом тильды (~).

Деструкторы не могут вызываться явно в С#, они не наследуются. Класс может иметь только один деструктор. Когда вызывается финализатор объекта, то вызывается каждый финализатор в цепочке наследования — от последнего к первому. Финализаторы выполняются в отдельном потоке CLR. Финализаторы и деструкторы это одно и то же, просто в разное время их переименовывали, по не скольку раз подряд, с чем это связано я не знаю.

Метод интерфейса IDisposable: void Dispose() – метод для освобождения ресурсов.

Важно!

Финализатор рекомендуется добавлять в код класса для освобождения неуправляемых ресурсов.

Dispose() рекомендуется применять для освобождения управляемых ресурсов.

Используйте формализованный шаблон очистки для того, чтобы обеспечить корректную работу как финализатора, так и Dispose().

Принудительно запустить сборку мусора с помощью метода GC.Collect(), можно в следующих случаях:

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

Основное назначение интерфейса IDisposable заключается в высвобождении неуправляемых ресурсов. Сборщик мусора автоматически высвобождает память, выделенную для управляемого объекта, если этот объект уже не используется. При использовании IDisposable ответственность за вызов метода Dispose возлагается на клиента. У клиента нет никакой возможности поручить системе или компилятору его автоматический вызов. При реализации метода Dispose класс обычно строится таким образом, что код финализатора повторно использует Dispose.  Ключевое слово using было перегружено для поддержки шаблона Disposable. Общая идея состояла в том, что оператор using должен захватывать ресурсы внутри
фигурных скобок, следующих за ключевым словом using, в то время как область видимости этих локальных переменных ограничена областью определения следующих далее фигурных скобок.  Оператор using расширяется до конструкции try/finally. Оператор using требует, чтобы все ресурсы, захваченные в процессе, были неявно преобразуемыми к IDisposable.

IDisposable — как альтернатива Деструктору, при реализации интерфейса, пользователь объекта должен вызвать метод Dispose()  перед завершением работы с объектом.

 

Метод GetTotalMemory() возвращает размер памяти в байтах которую занимают объекты в управляемой куче.

Метод Collect() — дает указание сборщику муссора проверить объекты определенного поколения, и в случаи если объект не используется будет уничтожен.

Метод WaitForPendingFinalizers() — приостанавливает выполнение текущего потока, пока не будут отработаны все объекты, предусматривающие финализацию. Вызывается обычно непосредственно после вызова GC.Collect().

Метод SuppressFinalize() — устанавливает флаг запрещения завершения для объектов которые в противном случае могли бы быть завершены сборщиком мусора. Отменяет работу деструктора для данного класса. GC.SuppressFinalize(this);

 

 

 

Обновлено: 25.06.2018 — 11:47

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.