Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Базовые технологии платформы .NET.pdf
Скачиваний:
66
Добавлен:
11.05.2015
Размер:
1.73 Mб
Скачать

7. Жизненный цикл объектов

Все типы платформы .NET делятся на ссылочные типы и типы значений. Переменные типов значений создаются в стеке, их время жизни ограничено тем блоком кода, в котором они объявляются. Например, если переменная типа значения объявлена в некотором методе, то после выхода из метода память в стеке, занимаемая переменной, автоматически освободится. Переменные ссылочного типа (объекты) располагаются в динамической памяти – управляемой куче (managed heap). Размещение объектов в управляемой куче происходит последовательно. Для этого CLR поддерживает указатель на свободное место в куче, перемещая его на соответствующее количество байтов после выделения памяти очередному объекту.

1)

A

B

C

D

свободная память

 

 

 

 

NextObjPtr

 

2)

A

B

C

D

E

свободная память

 

 

 

 

 

 

NextObjPtr

Рис. 1. Управляемая куча до и после выделения памяти под объект E.

7.1. Алгоритм сборки мусора

Если новый объект требует для размещения больше памяти, чем имеющийся свободный объем, CLR запускает процесс, называемый сборка мусора1 (garbage collection). На первом этапе сборки мусора строится граф используемых объектов. Отправными точками в построении графа являются корневые объекты. Это объекты следующих категорий:

локальная переменная или аргумент выполняемого метода (а также всех методов в стеке вызова);

статическое поле;

объект в очереди завершения (этот термин будет разъяснён позже).

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

1 Компилятор C# генерирует код, принудительно запускающий сборку мусора при окончании работы программы.

21

1)

A

B

C

D

E

F

 

 

 

 

 

 

 

 

 

 

 

 

NextObjPtr

2)

A

B

C

D

E

свободная память

3)

A

C

свободная память

4)

A

C

F

свободная память

 

 

 

 

NextObjPtr

Рис. 2. Различные фазы алгоритма сборки мусора.

При размещении и удалении объектов CLR использует ряд оптимизаций. Во-первых, объекты размером более 85000 байтов размещаются в отдельной управляемой куче больших объектов (Large Object Heap). При сборке мусора данная куча не дефрагментируется, так как копирование больших блоков памяти снижает производительность. Во-вторых, управляемая куча для малых объектов выделяет три поколения объектов – Gen0, Gen1 и Gen2. Вначале все объекты в куче относятся к Gen0. После первой сборки мусора те объекты, которые не были удалены, переходят в поколение Gen1, а новые объекты будут принадлежать Gen0. Вторая сборка мусора порождает поколение Gen2. Процесс сборки мусора работает с объектами старших поколений, только если освобождение памяти в младших поколениях дало неудовлетворительный результат.

Сборщик мусора представлен статическим классом System.GC, который обладает несколькими полезными методами (приведён неполный список):

1.Collect() – вызывает принудительную сборку мусора в программе.

2.GetGeneration() – возвращает номер поколения для указанного объекта;

3.SuppressFinalize() – подавляет вызов финализатора для объекта;

4.WaitForPendingFinalizers() – приостанавливает текущий поток выпол-

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

22

7.2. Финализаторы и интерфейс IDisposable

Обсудим автоматическую сборку мусора с точки зрения программиста, разрабатывающего некий класс. С одной стороны, такой подход имеет свои преимущества. В частности, практически исключаются случайные утечки памяти, которые могут вызвать «забытые» объекты. Однако объект может в процессе создания или работы резервировать неуправляемые системные ресурсы, которые нужно освободить после того, как объект перестаёт использоваться. Примерами таких ресурсов являются открытый операционной системой файл или сетевое подключение. Платформа .NET предлагает подход, обеспечивающий автоматическое освобождение ресурсов при уничтожении объекта. Тип System.Object содержит виртуальный метод Finalize(). Класс (но не структура!) может переопределить этот метод для освобождения неуправляемых ресурсов. Объект такого класса обрабатывается особо при размещении в куче и при сборке мусора. При размещении ссылка на объект запоминается в специальной внутренней структуре CLR. При сборке мусора эта ссылка перемещается в очередь завершения (freachable queue). Затем в отдельном программном потоке у объектов из очереди завершения происходит вызов метода Finalize(), после этого ссылка на объект удаляется из очереди завершения.

Язык C# не позволяет явно переопределить в пользовательском классе метод Finalize(). Вместо этого в классе описывается специальный финализатор. Имя финализатора имеет вид ~имя-класса(), он не имеет параметров и модификаторов доступа (считается, что у него модификатор доступа protected). При наследовании в финализатор класса-наследника автоматически подставляется вызов финализатора класса-предка.

Приведём пример класса с финализатором:

public class ClassWithFinalizer

{

public void DoSomething()

{

Console.WriteLine("I am working...");

}

~ClassWithFinalizer()

{

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

Console.WriteLine("Bye!");

}

}

Вызов метода Finalize() является недетерминированным. Для освобождения управляемых ресурсов (т. е. ресурсов платформы .NET) программист может описать в классе некий метод, который следует вызывать вручную, когда ресурс больше не нужен. Для унификации данного решения предлагается интерфейс IDisposable с единственным методом Dispose(). Если класс или

23

структура реализуют этот интерфейс, Dispose() содержит код освобождения управляемых ресурсов.

public class ClassWithDispose : IDisposable

{

public void DoSomething()

{

Console.WriteLine("I am working...");

}

public void Dispose()

{

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

Console.WriteLine("Bye!");

}

}

Язык C# имеет специальный оператор using, который гарантирует вызов метода Dispose() для заданных переменных в конце блока кода. Синтаксис оператора using следующий:

using (получение-ресурса) вложенный-оператор

Здесь получение-ресурса означает один из вариантов.

1.Объявление и инициализацию локальной переменой (или списка переменных). Тип переменной должен реализовывать IDisposable. Такая переменная в блоке using доступна только для чтения.

2.Выражение, значение которого имеет тип, реализующий IDisposable. Приведём пример использования оператора using:

using (ClassWithDispose x = new ClassWithDispose())

{

x.DoSomething();

// компилятор C# поместит сюда вызов x.Dispose()

}

7.3. Слабые ссылки

Слабая ссылка (weak reference) – особый вид ссылки на объект в системах со сборкой мусора. Если на объект имеются только слабые ссылки, он рассматривается алгоритмом сборки мусора как подлежащий удалению.

В .NET слабые ссылки представлены классом System.WeakReference. Конструктор класса принимает объект, на который создаётся слабая ссылка. Свойство Target указывает на этот объект, или имеет значение null, если объект был удалён сборщиком мусора.

var weak = new WeakReference(new StringBuilder("Test")); if (weak.IsAlive)

{

24