Автоматическое управление памятью (уборка мусора)
В этой главе книге Рихтера рассказано о создании новых объектов управляемыми приложениями, о том, как управляемая куча распоряжается временем жизни этих объектов и как освобождается занятая ими память. Мы рассмотрим работу уборщика мусора общеязыковой среды CLR и проблемы, связанные с его производительностью.
Управляемая куча
Любая программа использует ресурсы — файлы, буферы в памяти, пространство экрана, сетевые подключения, базы данных и т. п. В объектно-ориентированной среде каждый тип идентифицирует некий доступный этой программе ресурс. Чтобы им воспользоваться, должна быть выделена память для представления этого типа. Для доступа к ресурсу вам нужно
- Выделить память для типа, представляющего ресурс (обычно это делается при помощи оператора new в C#).
- Инициализировать выделенную память, установив начальное состояние ресурса и сделав его пригодным к использованию. За установку начального состояния типа отвечает его конструктор.
- Использовать ресурс, обращаясь к членам его типа (при необходимости операция может повторяться).
- В рамках процедуры очистки уничтожить состояние ресурса.
- Освободить память. За этот этап отвечает исключительно уборщик мусора.
Работа с памятью пораждает множество ошибок у программистов. Например, на языке C++ часто забывают освободить память или используют уже освободивщуюся. В C#, если вы не указывается ключевое слово unsafe, то управление памятью происходит автоматически, что заметно облегчает работу.
При использовании экземпляров типов, требующих специальной очистки, модель программирования остается такой же простой. Впрочем, иногда очистка ресурса должна выполняться как можно раньше, не дожидаясь вмешательства уборщика мусора. В таких классах можно вызвать дополнительный метод (называемый Dispose), чтобы очистка была выполнена по вашему собственному расписанию.
Выделение ресурсов из управляемой кучи
В CLR память для всех ресурсов выделяется из так называемой управляемой кучи (managed heap). При инициализации процесса CLR резервирует область адресного пространства под управляемую кучу, а также указатель, который я называю NextObjPtr. Он определяет, где в куче будет выделена память для следующего объекта, и изначально указывает на базовый адрес этой зарезервированной области адресного пространства. По мере заполнения области объектами CLR выделяет новые области, вплоть до заполнения всего адресного пространства. Таким образом, память приложения ограничивается виртуальным адресным пространством процесса. Для 32-разрядных процессов можно выделить до 1,5 гигабайта памяти, а для 64-разрядных процессов — около 8 терабайт памяти.
При выполнении оператора C# new среда CLR:
- Подситать количество байт, необходимых для хранения полей типа(и всех полей, унаследованных от базового типа);
- Прибавление к полученному значению количества байт, необходимых для хранения системынх полей объкета. У каждого объекта есть пара таких полей: указатель на объект-тип и индекс блока синхронизации. В 32-разрядных приложениях для каждого из этих полей требуется 32 бита, что увеличивает размер каждого объекта на 8 байт, а в 64-разрядных приложениях каждое поле занимает 64 бита, добавляя к каждому объекту 16 байт;
- Проверяет, хватает ли в зарезервированной области байтов на выделение памяти для объекта (при необходимости передает память). Если в управляемой куче достаточно места для объекта, ему выделяется память, начиная с адреса, на который ссылается указатель NextObjPtr, а занимаемые им байты обнуляются. Затем вызывается конструктор типа (передающий NextObjPtr в качестве параметра this), и оператор new возвращает ссылку на объект. Перед возвратом этого адреса NextObjPtr переходит на первый адрес после объекта, указывая на адрес, по которому в куче будет помещен следующий объект.
В среде, поддерживающей уборку мусора, новые объекты располагаются в памяти непрерывно, что повышает производительность за счет близкого расположения ссылок. В частности, это значит, что рабочий набор процесса будет меньше, чем у подобного приложения, работающего в неуправляемой среде. Также, скорее всего, все объекты, используемые в программе, уместятся в кэше процессора. Приложение сможет получать доступ к этим объектам с феноменальной скоростью, так как процессор будет выполнять большинство своих операций без кэш-промахов, замедляющих доступ к оперативной памяти.
Итак, пока складывается впечатление, что управляемая куча обладает превосходными характеристиками быстродействия. И все же это описание предполагает, что память всегда бесконечна, а CLR всегда может выделить блок для нового объекта. Конечно, это не так, поэтому управляемой куче необходим механизм уничтожения объектов, которые перестали быть нужными приложению. Таким механизмом является уборка мусора (Garbage Collection, GC).