.maxstack 1
.locals init (class SimpleGC.Car V_0)
IL_0000: nop
IL_0001: newobj instance void SimpleGC.Car::.ctor()
IL_0006: stloc.0
IL_0007: ret
} // end of method '<Program>$'::'<<Main>$>g__MakeACar|0_0'
Прежде чем ознакомиться с точными правилами, которые определяют момент, когда объект должен удаляться из управляемой кучи, давайте более подробно рассмотрим роль инструкции
newobj
языка CIL. Первым делом важно понимать, что управляемая куча представляет собой нечто большее, чем просто произвольную область памяти, к которой исполняющая среда имеет доступ. Сборщик мусора .NET Core "убирает" кучу довольно тщательно, при необходимости даже сжимая пустые блоки памяти в целях оптимизации.
Для содействия его усилиям в управляемой куче поддерживается указатель (обычно называемый указателем на следующий объект или указателем на новый объект), который идентифицирует точное местоположение, куда будет помещен следующий объект. Таким образом, инструкция
newobj
заставляет исполняющую среду выполнить перечисленные ниже основные операции.
1. Подсчитать общий объем памяти, требуемой для размещения объекта (в том числе память, необходимую для членов данных и базовых классов).
2. Выяснить, действительно ли в управляемой куче имеется достаточно пространства для сохранения размещаемого объекта. Если места хватает, то указанный конструктор вызывается, и вызывающий код в конечном итоге получает ссылку на новый объект в памяти, адрес которого совпадает с последней позицией указателя на следующий объект.
3. Наконец, перед возвращением ссылки вызывающему коду переместить указатель на следующий объект, чтобы он указывал на следующую доступную область в управляемой куче.
Описанный процесс проиллюстрирован на рис. 9.2.
В результате интенсивного размещения объектов приложением пространство внутри управляемой кучи может со временем заполниться. Если при обработке инструкции
newobj
исполняющая среда определяет, что в управляемой куче недостаточно места для размещения объекта запрашиваемого типа, тогда она выполнит сборку мусора, пытаясь освободить память. Соответственно, следующее правило сборки мусора выглядит тоже довольно простым.
Правило. Если в управляемой куче не хватает пространства для размещения требуемого объекта, то произойдет сборка мусора.
Однако то, как конкретно происходит сборка мусора, зависит от типа сборки мусора, используемого приложением. Различия будут описаны далее в главе.
Установка объектных ссылок в null
Программисты на C/C++ часто устанавливают переменные указателей в
null
, гарантируя тем самым, что они больше не ссылаются на какие-то местоположения в неуправляемой памяти. Учитывая такой факт, вас может интересовать, что происходит в результате установки в
null
ссылок на объекты в С#. В качестве примера измените метод
MakeACar()
следующим образом:
static void MakeACar()
{
Car myCar = new Car();
myCar = null;
}
Когда ссылке на объект присваивается
null
, компилятор C# генерирует код CIL, который гарантирует, что ссылка (
myCar
в данном примере) больше не указывает на какой-либо объект. Если теперь снова с помощью утилиты
ildasm.exe
просмотреть код CIL модифицированного метода
MakeACar()
, то можно обнаружить в нем код операции
ldnull
(заталкивает значение
null
в виртуальный стек выполнения), за которым следует код операции
stloc.0
(устанавливает для переменной ссылку
null
):
.method assembly hidebysig static
void '<<Main>$>g__MakeACar|0_0'() cil managed
{
// Code size 10 (0xa)
.maxstack 1
.locals init (class SimpleGC.Car V_0)
IL_0000: nop
IL_0001: newobj instance void SimpleGC.Car::.ctor()
IL_0006: stloc.0
IL_0007: ldnull
IL_0008: stloc.0
IL_0009: ret
} // end of method '<Program>$'::'<<Main>$>g__MakeACar|0_0'
Тем не менее, вы должны понимать, что присваивание ссылке значения
null
ни в коей мере не вынуждает сборщик мусора немедленно запуститься и удалить объект из кучи. Единственное, что при этом достигается — явный разрыв связи между ссылкой и объектом, на который она ранее указывала. Таким образом, установка ссылок в null в C# имеет гораздо меньше последствий, чем в других языках, основанных на С; однако никакого вреда она определенно не причиняет.
Выяснение, нужен ли объект
Теперь вернемся к вопросу о том, как сборщик мусора определяет момент, когда объект больше не нужен. Для выяснения, активен ли объект, сборщик мусора использует следующую информацию.
• Корневые элементы в стеке: переменные в стеке, предоставляемые компилятором и средством прохода по стеку.
• Дескрипторы сборки мусора: дескрипторы, указывающие на объекты, на которые можно ссылаться из кода или исполняющей среды.
• Статические данные: статические объекты в доменах приложений, которые могут ссылаться на другие объекты.
Во время процесса сборки мусора исполняющая среда будет исследовать объекты в управляемой куче с целью выяснения, являются ли они по-прежнему достижимыми (т.е. корневыми) для приложения. Для такой цели исполняющая среда будет строить граф объектов, который представляет каждый достижимый объект в куче. Более подробно графы объектов объясняются во время рассмотрения сериализации объектов в главе 20. Пока достаточно знать, что графы объектов применяются для документирования всех достижимых объектов. Кроме того, имейте в виду, что сборщик мусора никогда не будет создавать граф для того же самого объекта дважды, избегая необходимости в выполнении утомительного подсчета циклических ссылок, который характерен при программировании СОМ.