После изложения основных деталей, касающихся процесса сборки мусора, будет показано, каким образом программно взаимодействовать со сборщиком мусора, используя класс
System.GC
(что в большинстве проектов обычно не требуется). Мы рассмотрим, как с применением виртуального метода
System.Object.Finalize
и интерфейса
IDisposable
строить классы, которые своевременно и предсказуемо освобождают внутренние неуправляемые ресурсы.
Кроме того, будут описаны некоторые функциональные возможности сборщика мусора, появившиеся в версии .NET 4.0, включая фоновую сборку мусора и ленивое (отложенное) создание объектов с использованием обобщенного класса
System.Lazy<>
. После освоения материалов данной главы вы
должны хорошо понимать, каким образом исполняющая среда управляет объектами .NET Core.
Классы, объекты и ссылки
Прежде чем приступить к исследованию основных тем главы, важно дополнительно прояснить отличие между классами, объектами и ссылочными переменными. Вспомните, что класс — всего лишь модель, которая описывает то, как экземпляр такого типа будет выглядеть и вести себя в памяти. Разумеется, классы определяются внутри файлов кода (которым по соглашению назначается расширение
*.cs
). Взгляните на следующий простой класс
Car
, определенный в новом проекте консольного приложения C# по имени
SimpleGC
:
namespace SimpleGC
{
// Car.cs
public class Car
{
public int CurrentSpeed {get; set;}
public string PetName {get; set;}
public Car{}
public Car(string name, int speed)
{
PetName = name;
CurrentSpeed = speed;
}
public override string ToString
=> $"{PetName} is going {CurrentSpeed} MPH";
}
}
}
После того как класс определен, в памяти можно размещать любое количество его объектов, применяя ключевое слово new языка С#. Однако следует иметь в виду, что ключевое слово
new
возвращает ссылку на объект в куче, а не действительный объект. Если ссылочная переменная объявляется как локальная переменная в области действия метода, то она сохраняется в стеке для дальнейшего использования внутри приложения. Для доступа к членам объекта в отношении сохраненной ссылки необходимо применять операцию точки С#:
using System;
using SimpleGC;
Console.WriteLine("***** GC Basics *****");
// Создать новый объект Car в управляемой куче.
// Возвращается ссылка на этот объект (refToMyCar).
Car refToMyCar = new Car("Zippy", 50);
// Операция точки (.) используется для обращения к членам
// объекта с применением ссылочной переменной.
Console.WriteLine(refToMyCar.ToString);
Console.ReadLine;
На заметку! Вспомните из главы 4, что структуры являются типами значений, которые всегда размещаются прямо в стеке и никогда не попадают в управляемую кучу .NET Core. Размещение в куче происходит только при создании экземпляров классов.
Базовые сведения о времени жизни объектов
При создании приложений C# корректно допускать, что исполняющая среда .NET Core позаботится об управляемой куче без вашего прямого вмешательства. В действительности "золотое правило" по управлению памятью в .NET Core выглядит простым.
Правило.
Используя ключевое слово
new
, поместите экземпляр класса в управляемую кучу и забудьте о нем.
После создания объект будет автоматически удален сборщиком мусора, когда необходимость в нем отпадет. Конечно, возникает вполне закономерный вопрос о том, каким образом сборщик мусора выясняет, что объект больше не нужен? Краткий (т.е. неполный) ответ можно сформулировать так: сборщик мусора удаляет объект из кучи, только когда он становится недостижимым для любой части кодовой базы. Добавьте в класс
Program
метод, который размещает в памяти локальный объект
Car
:
static void MakeACar
{
// Если myCar - единственная ссылка на объект Car, то после
// завершения этого метода объект Car *может* быть уничтожен.
Car myCar = new Car;
}
Обратите внимание, что ссылка на объект
Car(myCar)
была создана непосредственно внутри метода
MakeACar
и не передавалась за пределы определяющей области видимости (через возвращаемое значение или параметр
ref/out
). Таким образом, после завершения данного метода ссылка
myCar
оказывается недостижимой, и объект
Car
теперь является кандидатом на удаление сборщиком мусора. Тем не менее, важно понимать, что восстановление занимаемой этим объектом памяти немедленно после завершения метода
MakeACar
гарантировать нельзя. В данный момент можно предполагать лишь то, что когда исполняющая среда инициирует следующую сборку мусора, объект
myCar
может быть безопасно уничтожен.
Как вы наверняка сочтете, программирование в среде со сборкой мусора значительно облегчает разработку приложений. И напротив, программистам на языке C++ хорошо известно, что если они не позаботятся о ручном удалении размещенных в куче объектов, тогда утечки памяти не заставят себя долго ждать. На самом деле отслеживание утечек памяти — один из требующих самых больших затрат времени (и утомительных) аспектов программирования в неуправляемых средах. За счет того, что сборщику мусора разрешено взять на себя заботу об уничтожении объектов, обязанности по управлению памятью перекладываются с программистов на исполняющую среду.
Код CIL для ключевого слова new
Когда компилятор C# сталкивается с ключевым словом new, он вставляет в реализацию метода инструкцию
newobj
языка CIL. Если вы скомпилируете текущий пример кода и заглянете в полученную сборку с помощью утилиты
ildasm.ехе
, то найдете внутри метода
MakeACar
следующие операторы CIL:
.method assembly hidebysig static
void '<<Main>$>g__MakeACar|0_0' cil managed
{
// Code size 8 (0x8)
.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 "убирает" кучу довольно тщательно, при необходимости даже сжимая пустые блоки памяти в целях оптимизации.