Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



Программистам на С# никогда не приходится непосредственно удалять управляемый объект из памяти (в языке С# нет даже ключевого слова вроде delete). Вместо этого объекты.NET размещаются в области памяти, которая называется управляемой кучей (managed heap), откуда они автоматически удаляются сборщиком мусора, когда наступает " определенный момент в будущем".

Для начала вспомним, что класс представляет собой схему, которая описывает то, каким образом экземпляр данного типа должен выглядеть и вести себя в памяти. Определяются классы в файлах кода (которым по соглашению назначается расширение *.cs). Как только класс определен, с использованием ключевого слова new, поддерживаемого в С#, можно размещать в памяти любое количество его объектов. Однако при этом следует помнить, что ключевое слово new возвращает ссылку на объект в куче, а не фактический объект. Если ссылочная переменная объявляется как локальная переменная в контексте метода, она сохраняется в стеке для дальнейшего использования в приложении. Для вызова членов объекта к сохраненной ссылке должна применяться операция точки С#.

На рис. 4 схематично показаны отношения между классами, объектами и ссылками на них.

 
 

 


Ссылки на объекты в управляемой куче

 

 

Рис. 4. Отношения между классами объектами и ссылками

 

При создании приложений на С# можно смело полагать, что исполняющая среда.NET будет сама заботиться об управляемой куче без непосредственного вмешательства со стороны программиста. На самом деле " золотое правило" по управлению памятью в.NET звучит просто: «Размещайте объект в управляемой куче с использованием ключевого слова new и забывайте об этом.»

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

Прежде чем ознакомиться с точными правилами, которые определяют момент, когда объект должен удаляться из управляемой кучи, нужно рассмотрим роль CIL-инструкции newobj, которая вставляется компилятором C# в реализацию метода при обнаружении ключевого слова new. Управляемая куча представляет собой нечто большее, чем просто случайный фрагмент памяти, к которому CLR получает доступ. Сборщик мусора.NET " убирает" кучу довольно тщательно, причем (при необходимости) даже сжимает пустые блоки памяти с целью оптимизации. Чтобы ему было легче это делать, в управляемой куче поддерживается указатель (обычно называемый указателем на следующий объект или указателем на новый объект), который показывает, где точно будет размещаться следующий объект.

Таким образом, инструкция newobj заставляет CLR-среду выполнить перечисленные ниже ключевые операции.

• Вычислить, сколько всего памяти требуется для размещения объекта (в том числе памяти, необходимой для членов данных и базовых классов).

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

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

Из-за постоянного размещения объектов приложением пространство в управляемой куче может со временем заполниться. В случае если при обработке следующей инструкции newobj среда CLR обнаруживает, что в управляемой куче не хватает пространства для размещения запрашиваемого типа, она приступает к сборке мусора и тем самым пытается освободить хоть сколько-то памяти.

Теперь снова вернемся к вопросу о том, каким образом сборщик мусора определяет момент, когда объект уже более не нужен. Чтобы разобраться в стоящих за этим деталях, необходимо знать, что собой представляет корневые элементы приложения (application roots). Корневым элементом (root) называется ячейка в памяти, в которой содержится ссылка на размещающийся в куче объект.

Во время процесса сборки мусора исполняющая среда будет исследовать объекты в управляемой куче, чтобы определить, являются ли они по-прежнему достижимыми (т.е. корневыми) для приложения. Для этого среда CLR будет создавать графы объектов, представляющие все достижимые для приложения объекты в куче.

Чтобы увидеть все это на примере, предположим, что в управляемой куче содержится набор объектов с именами A, B, C, D, E, F, G. Во время сборки мусора эти объекты (а также любые внутренние объектные ссылки, которые они могут содержать) будут исследованы на предмет наличия у них активных корневых элементов. После построения графа все недостижимые объекты (которыми в примере пусть будут объекты С и F) помечаются как являющиеся мусором. После того как объект помечен для уничтожения (в данном случае это объекты С и F, поскольку в графе объектов они во внимание не принимаются), они будут удалены из памяти. Оставшееся пространство в куче будет после этого сжиматься до компактного состояния, что, в свою очередь, вынудит CLR изменить набор активных корневых элементов приложения (и лежащих в их основе указателей) так, чтобы они ссылались на правильное место в памяти (это делается автоматически и прозрачно). И, наконец, указатель на следующий объект тоже будет подстраиваться так, чтобы указывать на следующий доступный участок памяти.

При попытке обнаружить недостижимые объекты CLR-среда не проверяет буквально каждый находящийся в куче объект. Для оптимизации процесса каждый объект в куче относится к определенному " поколению". Смысл в применении поколений выглядит довольно просто: чем дольше объект находится в куче, тем выше вероятность того, что он там и будет оставаться. Например, класс, определенный в главном окне настольного приложения, будет оставаться в памяти вплоть до завершения выполнения программы. С другой стороны, объекты, которые были размещены в куче лишь недавно (как, например, те, что находятся в пределах области действия метода), вероятнее всего будут становиться недостижимым довольно быстро. Исходя из этих предположений, каждый объект в куче относится к одному из перечисленных ниже поколений.

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

• Поколение 1. Идентифицирует объект, который уже " пережил" один процесс сборки мусора (был помечен как подлежащий удалению в процессе сборки мусора, но не был удален из-за наличия достаточного места в куче).

• Поколение 2. Идентифицирует объект, которому удалось пережить более одного прогона сборщика мусора.

Сборщик мусора сначала анализирует все объекты, которые относятся к поколению 0. Если после их удаления остается достаточное количество памяти, статус всех остальных (уцелевших) объектов повышается до поколения 1.

Если все объекты поколения 0 уже были проверены, но все равно требуется дополнительное пространство, проверяться на предмет достижимости и подвергаться процессу сборки мусора начинают объекты поколения 1. Объектам поколения 1, которым удалось уцелеть после этого процесса, затем назначается статус объектов поколения 2.

Если же сборщику мусора все равно требуется дополнительная память, тогда на предмет достижимости начинают проверяться и объекты поколения 2. Объектам, которым удается пережить сборку мусора на этом этапе, оставляется статус объектов поколения 2, поскольку более высокие поколения просто не поддерживаются.

Таким образом более новые объекты (вроде локальных переменных) будут удаляться быстрее, а более старые (такие как объекты приложений) — реже.

Тип System.CG

В библиотеках базовых классов доступен класс по имени System. GC, который позволяет программно взаимодействовать со сборщиком мусора за счет обращения к его статическим членам. Обычно единственным случаем, когда нужно применять члены System. GC, является создание классов, предусматривающих использование на внутреннем уровне неуправляемых ресурсов.

Ниже приведены краткие описания некоторых наиболее интересных членов класса System.GC:

1) AddMemorуPressure(), RemoveMemoryPressure() - gозволяют указывать числовое значение, отражающее " уровень срочности", который вызывающий объект применяет в отношении к сборке мусора. Следует иметь в виду, что эти методы должны изменять уровень давления в тандеме и, следовательно, никогда не устранять больше давления, чем было добавлено.

2) Collect() - заставляет сборщик мусора провести сборку мусора. Должен быть перегружен так, чтобы указывать, объекты какого поколения подлежат сборке, а также какой режим сборки использовать (с помощью перечисления GCCollectionMode).

3) CollectionCount() - возвращает числовое значение, показывающее, сколько раз объектам данного поколения удалось переживать процесс сборки мусора.

4) GetGeneration() - возвращает информацию о том, к какому поколению в настоящий момент относится объект.

5) GetTotalMemory() - возвращает информацию о том, какой объем памяти (в байтах) в настоящий момент занят в управляемой куче. Булевский параметр указывает, должен ли вызов сначала дождаться выполнения сборки мусора, прежде чем возвращать результат.

6) MaxGeneration - возвращает информацию о том, сколько максимум поколений поддерживается в целевой системе.

7) SuppressFinalize() - позволяет устанавливать флаг, указывающий, что для данного объекта не должен вызываться его метод Finalize()

8) WaitForPendingFinalizers() - позволяет приостанавливать выполнение текущего потока до тех пор, пока не будут финализированы все объекты, предусматривающие финализацию. Обычно вызывается сразу же после вызова метода GC.Collect()

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

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

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

Если выяснилось, что выполнение сборщиком мусора проверки на предмет наличия недостижимых объектов может быть выгодным, можно инициировать процесс сборки мусора явным образом, как показано ниже:

static void Main(string [ ] args)

{

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

// ожидание завершения финализации каждого из объектов.

GC.Collect();

GC.WaitForPendingFinalizers();

}

В случае принудительной активизации сборки мусора нужно также не забывать вызвать метод GC.WaitForPendingFinalizers(). Это дает возможность всем финализируемым объектам произвести любую необходимую очистку перед продолжением работы программы. Метод GC.WaitForPendingFinalizers() незаметно приостанавливает выполнение вызывающего " потока" во время процесса сборки мусора, что очень хорошо, поскольку исключает вероятность вызова в коде каких-либо методов на объекте, который в текущий момент уничтожается.

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

Например, чтобы CLR-среда анализировала только объекты поколения 0, необходимо использовать следующий код:

static void Main(string [ ] args)

{

// Исследование только объектов поколения 0.

GC.Collect(0);

GC.WaitForPendingFinalizers ();

}

Вдобавок методу Collect() во втором параметре может передаваться значение перечисления GCCollectionMode, которое позволяет более точно указать, каким образом исполняющая среда должна принудительно инициировать сборку мусора. Ниже показаны значения, доступные в этом перечислении:

public enum GCCollectionMode

{

Default, // Текущим значением по умолчанию является Forced.

Forced, // Указывает исполняющей среде начать сборку мусора //немедленно

Optimized // Позволяет исполняющей среде выяснить, оптимален //ли настоящий момент для удаления объектов.

}

Как и при любой сборке мусора, в случае вызова GC.Collect() уцелевшим объектам назначается статус объектов более высокого поколения.

 

Финализация объектов

В базовом классе.NET — System.Object — имеется виртуальный метод по имени Finalize(), реализация которого по умолчанию выглядит следующим образом:

// Класс System.Object

public class Object

{

protected virtual void Finalize(){}

}

За счет его переопределения в специальных классах устанавливается специфическое место для выполнения любой необходимой данному типу логики по очистке. Из-за того, что метод Finalize() по определению является защищенным (protected), вызывать его напрямую из класса экземпляра с помощью операции точки не допускается. Вместо этого метод Finalize () (если он поддерживается) будет автоматически вызываться сборщиком мусора перед удалением соответствующего объекта из памяти.

Вызов метода Finalize() будет происходить либо во время естественной активизации процесса сборки мусора, либо во время его принудительной активизации программным образом с помощью GC. Collect().

При переопределении метода Finalize() в C# нельзя применятьключевое слово override:

class MyResourceWrapper

{

// Ошибка на этапе компиляции

protected override void Finalize () { }

}

Вместо этого для достижения того же эффекта должен применяться синтаксис деструктора (подобно C++). Финализаторы в С# очень похожи на конструкторы тем, что именуются идентично классу, внутри которого определены. Помимо этого, они сопровождаются префиксом в виде тильды (~). В отличие от конструкторов, однако, они никогда не снабжаются модификатором доступа (поскольку всегда являются неявно защищенными), не принимают никаких параметров и не могут быть перегружены (в каждом классе может присутствовать только один финализатор).

Ниже приведен пример написания специального финализатора для класса MyClass, который при вызове заставляет систему выдавать звуковой сигнал.

// Переопределение System.Object.Finalize()

//с использованием синтаксиса финализатора.

class MyClass

{

~MyClass()

{

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

//Обеспечение подачи звукового сигнала при

// уничтожении объекта (только в целях //тестирования).

Console.Beep();

}

}

При размещении объекта в управляемой куче исполняющая среда автоматически определяет, поддерживается ли в нем какой-нибудь специальный метод Finalize(). Если да, тогда она помечает его как финализируемый (finalizable) и сохраняет указатель на него во внутренней очереди, называемой очередью финализации (finalization queue). Эта очередь финализации представляет собой просматриваемую сборщиком мусора таблицу, где перечислены объекты, которые перед удалением из кучи должны быть обязательно финализированы.

Когда сборщик мусора определяет, что наступило время удалить объект из памяти, он проверяет каждую запись в очереди финализации и копирует объект из кучи в еще одну управляемую структуру, называемую таблицей объектов, доступных для финализации (finalization reachable table). После этого он создает отдельный поток для вызова метода Finalize() в отношении каждого из упоминаемых в этой таблице объектов при следующей сборке мусора. В результате получается, что для окончательной финализации объекта требуется как минимум два процесса сборки мусора.

 


Поделиться:



Популярное:

  1. I. Сохранять и укреплять здоровье детей, формировать у них привычку к здоровому образу жизни
  2. I.12. Факторы жизни растений, возможность управления ими с помощью агротех. приёмов.
  3. II. Время и место проведения. Заявки.
  4. II. Иные преступления против жизни
  5. II. Обладание и бытие в повседневной жизни
  6. II. Отметьте чудные дела земной жизни Христа.
  7. II.II.II Роль Городецкого краеведческого музея в культурной жизни города
  8. SWOT-анализ объекта исследования, группировка ключевых факторов повышения уровня жизни населения
  9. V. ПОДКРЕПЛЕНИЕ В ПОВСЕДНЕВНОЙ ЖИЗНИ
  10. Адаптивный иммунитет вырабатывается в процессе жизни индивида и представляет собой специфическую защитную реакцию организма на конкретный чужеродный агент (антиген) с участием лимфоцитов.
  11. Айкидо – это искусство внутренней гармонии и бесконфликтного харизматичного общения в жизни и в бизнесе, основанное на принципах айкидо.
  12. Александр – хронометрист, ремонтник Алексей – фотограф, видеооператор


Последнее изменение этой страницы: 2016-07-14; Просмотров: 729; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.049 с.)
Главная | Случайная страница | Обратная связь