
- •С# и объектно-ориентированное программирование. Содержание
- •Разбор простой программы на с#.
- •Варианты метода Main ()
- •Формальное определение класса в с#
- •2.1Ссылки на самого себя
- •Построение цепочки вызовов конструкторов с использованием this.
- •Модификаторы доступа с#
- •Средства инкапсуляции с#
- •Инкапсуляция с использованием традиционных методов доступа и изменения
- •Инкапсуляция с использованием свойств .Net
- •Свойства, доступные только для чтения и только для записи
- •Статические свойства
- •Статические конструкторы
- •Наследование в с#
- •Работа с конструктором базового класса
- •Множественное наследование.
- •Ключевое слово sealed
- •Поддержка полиморфизма в с#
- •Ключевые слова virtual и override
- •Абстрактные классы
- •Полиморфный интерфейс
- •Сокрытие методов
- •Правила приведения к базовому и производному классу
- •Ключевое слово as
- •Ключевое слово is
- •Применение модели включения – делегирования.
- •Определение вложенных типов
- •Обработка исключений
- •Роль обработки исключений в .Net
- •Составляющие процесса обработки исключений в .Net
- •Генерация общего исключения
- •Перехват исключений
- •Создание специальных исключений, способ первый
- •Обработка нескольких исключений.
- •Блок finally
- •Замечания по работе с исключениями
- •Время жизни объектов
- •Базовые сведения о времени жизни объектов
- •Роль корневых элементов приложения
- •Поколения объектов
- •Параллельная сборка мусора в версиях .Net 1.0 - .Net 3.5
- •Фоновая сборка мусора в версии .Net 4.0
- •Создание финализируемых объектов
- •Описание процесса финализации
- •Создание высвобождаемых объектов
- •Повторное использование ключевого слова using в с#
- •Взаимодействие со сборщиком мусора
- •Принудительная активизация сборки мусора
- •Создание финализируемых и высвобождаемых типов
- •Формализованный шаблон очистки
Роль корневых элементов приложения
Вернемся к вопросу о том, каким образом сборщик мусора определяет момент, когда объект уже более не нужен. Чтобы разобраться в стоящих за этим деталях, необходимо знать, что собой представляет корневые элементы приложения (application roots). Попросту говоря, корневым элементом (root) называется ячейка в памяти, в которой содержится ссылка на размещающийся в куче объект.
Строго говоря, корневыми могут называться элементы любой из перечисленных ниже категорий:
Ссылки на глобальные объекты (хотя в С# они не разрешены, CIL-код позволяет размещать глобальные объекты).
Ссылки на любые статические объекты или статические поля.
Ссылки на локальные объекты в пределах кодовой базы приложения.
Ссылки на передаваемые методу параметры объектов.
Ссылки на объекты, ожидающие финализации (об этом подробно далее).
Любые регистры центрального процессора, которые ссылаются на объект.
Во время процесса сборки мусора исполняющая среда будет исследовать объекты в управляемой куче, чтобы определить, являются ли они по-прежнему достижимыми (т.е. корневыми) для приложения. Для этого среда CLR будет создавать графы объектов, представляющие все достижимые для приложения объекты в куче (графы применяются для документирования всех достижимых объектов). Кроме того, следует иметь в виду, что сборщик мусора никогда не будет создавать граф для одного и того же объекта дважды, избегая необходимости выполнения подсчета циклических ссылок, который характерен для программирования в среде СОМ.
Ч
тобы
увидеть все это на примере, предположим,
что в управляемой куче содержится набор
объектов с именами A,B,C,D,E,F,G. Во время
сборки мусора эти объекты (а также
любые внутренние объектные ссылки,
которые они могут содержать) будут
исследованы на предмет наличия у них
активных корневых элементов.
После построения графа все недостижимые объекты (которыми в примере пусть будут объекты С и F) помечаются как являющиеся мусором. На рис. 3 показано, как примерно выглядит граф объектов в только что описанном сценарии (линии со стрелками следует воспринимать как "зависит от" или "требует"; например, "Е зависит от G и В", «А не зависит ни от чего" и т.д.).
П
осле
того как объект помечен для уничтожения
(в данном случае это объекты С и F,
поскольку в графе объектов они во
внимание не принимаются), они будут
удалены из памяти. Оставшееся пространство
в куче будет после этого сжиматься до
компактного состояния, что, в свою
очередь, вынудит CLR изменить набор
активных корневых элементов приложения
(и лежащих в их основе указателей) так,
чтобы они ссылались на правильное место
в памяти (это делается автоматически и
прозрачно). И, наконец, указатель на
следующий объект тоже будет подстраиваться
так, чтобы указывать на следующий
доступный участок памяти. На рис. 4
показано, как выглядит конечный результат
этих изменений в рассматриваемом
сценарии.
Поколения объектов
При попытке обнаружить недостижимые объекты CLR-среда не проверяет буквально каждый находящийся в куче объект. Очевидно, что на это уходила бы масса времени, особенно в более крупных (реальных) приложениях. Для оптимизации процесса каждый объект в куче относится к определенному "поколению". Смысл в применении поколений выглядит довольно просто: чем дольше объект находится в куче, тем выше вероятность того, что он там и будет оставаться.
Например, класс, определенный в главном окне настольного приложения, будет оставаться в памяти вплоть до завершения выполнения программы. С другой стороны, объекты, которые были размещены в куче лишь недавно (как, например, те, что находятся в пределах области действия метода), вероятнее всего будут становиться недостижимым довольно быстро. Исходя из этих предположений, каждый объект в куче относится к одному из перечисленных ниже поколений.
Поколение 0. Идентифицирует новый только что размещенный объект, который еще никогда не помечался как подлежащий удалению в процессе сборки мусора.
Поколение 1. Идентифицирует объект, который уже "пережил" один процесс сборки мусора (был помечен как подлежащий удалению в процессе сборки мусора, но не был удален из-за наличия достаточного места в куче).
Поколение 2. Идентифицирует объект, которому удалось пережить более одного прогона сборщика мусора.
На заметку! Поколения 0 и 1 называются эфемерными (недолговечными).
С
борщик
мусора сначала анализирует все объекты,
которые относятся к поколению 0. Если
после их удаления остается достаточное
количество памяти, статус всех остальных
(уцелевших) объектов повышается до
поколения 1. Чтобы увидеть, как поколение,
к которому относится объект, влияет на
процесс сборки мусора, обратите внимание
на рис. 5, где схематически показано,
как набору уцелевших объектов поколения
О (А, В и Е) назначается статус объектов
следующего поколения после освобождения
требуемого объема памяти.
Если все объекты поколения 0 уже были проверены, но все равно требуется дополнительное пространство, проверяться на предмет достижимости и подвергаться процессу сборки мусора начинают объекты поколения 1. Объектам поколения 1, которым удалось уцелеть после этого процесса, затем назначается статус объектов поколения 2. Если же сборщику мусора все равно требуется дополнительная память, тогда на предмет достижимости начинают проверяться и объекты поколения 2. Объектам, которым удается пережить сборку мусора на этом этапе, оставляется статус объектов поколения 2, поскольку более высокие поколения просто не поддерживаются. Из всего вышесказанного важно сделать следующий вывод: из-за отнесения объектов в куче к определенному поколению, более новые объекты (вроде локальных переменных) будут удаляться быстрее, а более старые (такие как объекты приложений) — реже.