Виртуальная система выполнения (Virtual Execution System - VES) представляет собой абстрактную виртуальную машину, способную выполнять управляемый код. Можно сказать, что виртуальная система выполнения существует только "на бумаге", потому что ни одна из реализаций CLI не содержит интерпретатора CIL-кода (вместо этого используется JIT-компилятор, транслирующий инструкции CIL в команды процессора). Другими словами, виртуальная система выполнения не зря называется виртуальной (то есть мнимой), ее предназначение - служить образцом, которому должна соответствовать любая реализация CLI. Какую бы технологию ни использовала эта реализация для выполнения программ, эта технология должна работать так же, как работала бы виртуальная система выполнения.
Если сравнить CLI с ее ближайшим конкурентом - платформой Java, можно прийти к выводу, что VES является значительно более абстрактной моделью, чем виртуальная машина Java (
Изучение работы виртуальной машины CLI заключается в том, чтобы понять, что представляет собой состояние виртуальной машины и как это состояние меняется во времени. В этом разделе мы не будем затрагивать вопрос изменения состояния, так как оно связано с выполнением инструкций языка CIL, разговор о котором мы отложим до третьей главы нашего учебника.
На рис. 1.9 показана схема состояния виртуальной машины, из которой видно, что виртуальная машина может выполнять сразу несколько нитей (threads). Как уже говорилось ранее, виртуальная машина является всего лишь моделью поведения конкретных реализаций CLI, поэтому мы будем предполагать, что все нити выполняются параллельно. На самом деле, нити могут работать как параллельно, так и в режиме вытесняющей многозадачности, могут отображаться на процессы или на нити операционной системы, а могут и не отображаться. Сейчас для нас такие детали не имеют значения.
(рис 1.9) Состояние виртуальной машины
Состояние виртуальной машины является совокупностью состояний нитей и состояния кучи.
Состояние нити представляет собой
Такая схема состояния нити является абстракцией традиционной модели последовательности вызовов методов. Традиционная модель предполагает наличие для каждой нити единого стека, в котором для каждого вызванного метода размещаются его параметры, локальные переменные, а также служебная информация, необходимая для обеспечения возврата управления в вызвавший метод. Разработчики спецификации CLI сознательно отказались от использования в явном виде единого
Состояние кучи определяется состояниями содержащихся в ней объектов. Спецификация VES содержит упоминание о том, что допустимо существование сразу нескольких куч (например, реализация CLI может ранжировать объекты по размеру и использовать для их хранения разные кучи). Однако на рис. 1.9 изображена только одна куча, так как, по нашему мнению, количество куч определяется конкретными реализациями CLI и для понимания работы VES не существенно.
Спецификация VES предполагает, что для удаления ненужных объектов из кучи будет использоваться какой-либо алгоритм автоматического управления памятью. При этом детали такого алгоритма не рассматриваются, то есть в реализациях CLI могут применяться различные алгоритмы сборки мусора.
В заключение необходимо отметить, что состояния нитей и состояние кучи должны находиться в
На рис. 1.10 изображена схема состояния метода. Элементы состояния метода можно условно разделить на две группы: изменяемые данные и неизменяемые данные. Изменяемые данные доступны из тела метода для чтения и записи, в то время как неизменяемые данные либо доступны только для чтения либо вообще предназначены для внутреннего использования в системе выполнения.
(рис 1.10) Состояние метода
Давайте перечислим элементы состояния метода, входящие в группу изменяемых данных:
Содержит адрес следующей инструкции в
Виртуальная система выполнения работает по принципу стекового процессора. Это означает, что операнды инструкций, а также возвращаемые инструкциями значения хранятся в специальной области памяти, а именно на стеке вычислений.
Каждое состояние метода имеет собственный стек вычислений, содержимое которого сохраняется при вызове методов (то есть, если наш метод вызывает другой метод, то по завершении работы вызванного метода содержимое стека никуда не денется).
Для хранения локальных переменных в состоянии метода предусмотрена отдельная область памяти, состоящая из так называемых слотов (
Фактические параметры, переданные методу, записываются в специальную область памяти, которая организована так же, как и область локальных переменных.
В языке CIL предусмотрена инструкция localloc, которая позволяет динамически размещать объекты в области памяти, локальной для метода. Объекты в этой области живут до тех пор пока метод не завершится.
Обратите внимание, что стек вычислений, локальные переменные и параметры, а также локальные данные метода представляют собой логически отдельные области памяти. Каждая конкретная реализация CLI самостоятельно решает вопрос, где размещать эти области.
В группу неизменяемых данных входят следующие элементы состояния метода:
Содержит
Описатель метода доступен из кода метода, но в основном он используется системой выполнения при сборке мусора и обработке исключений.
Используется системой безопасности CLI и недоступен из кода метода.
Служит для организации списка состояний методов внутри системы выполнения и недоступно из кода метода. Фактически представляет собой указатель на состояние метода, из тела которого был вызван текущий метод.
Итак, несмотря на то, что большинство современных процессоров для организации вычислений используют регистры, в виртуальной системе выполнения вместо регистров применяется стек вычислений. Это связано, скорее всего, с тем, что стековые вычисления достаточно легко можно отобразить на регистры процессора, так как модель, использующая стек, более абстрактна, чем регистровая модель.
Стек вычислений в VES состоит из слотов. При этом глубина стека (максимальное количество слотов) всегда ограничена и задается статически в заголовке метода. Решение ограничить глубину стека было принято разработчиками спецификации CLI для того, чтобы облегчить создание JIT-компиляторов.
На входе метода стек вычислений всегда пуст. Затем он используется для передачи операндов инструкциям CIL, для передачи фактических параметров вызываемым методам, а также для получения результатов выполнения инструкций и вызываемых методов. Если метод возвращает какое-то значение, то оно кладется на стек вычислений перед завершением метода.
Важной особенностью организации стека вычислений является то обстоятельство, что его слоты не адресуются, то есть мы не можем получить указатель на какой-либо слот и записать в него значение.
Каждый слот стека вычислений может содержать ровно одно значение одного из следующих типов:
int64 - 8-байтовое целое со знаком;int32 - 4-байтовое целое со знаком;native int - знаковое целое, разрядность которого зависит от аппаратной платформы (может быть 4 или 8 байт);F - число с плавающей точкой, разрядность которого зависит от аппаратной платформы (не может быть меньше 8 байт); - управляемый указатель;O - объектная ссылка;Таким образом, слоты стека вычислений могут иметь различный размер в зависимости от типов записанных в них значений.
Также мы можем видеть, что допустимые типы значений для стека вычислений не совпадают с общей системой типов F стека вычислений не имеет аналога в и O соответственно.
Давайте обсудим, как в VES осуществляется работа с типами данных, не поддерживаемыми напрямую стеком вычислений.
Во-первых, короткие целые типы ( bool, char, int8, int16, unsigned int8, unsigned int16 ) при загрузке на стек вычислений расширяются до int32. При этом знаковые короткие целые типы ( int8, int16 ) расширяются с сохранением знака, а беззнаковые расширяются путем добавления нулевых битов. При сохранении значения со стека вычислений в переменной, параметре, поле объекта или элементе массива происходит обратное сужающее преобразование.
Во-вторых, беззнаковый тип unsigned int32 при загрузке на стек вычислений становится знаковым int32, и аналогично, беззнаковый unsigned int64 становится знаковым int64. При этом, естественно, никаких преобразований не происходит - просто последовательность бит, которая раньше считалась беззнаковым целым, копируется на стек вычислений. Вообще говоря, утверждение, что целые типы int32, int64 и native int на стеке вычислений имеют знак, достаточно спорно. Правильнее было бы сказать, что они могут представлять как знаковые, так и беззнаковые целые числа в зависимости от того, какие инструкции CIL используются для их обработки.
В-третьих, типы float32 и float64 при копировании на стек вычислений преобразуются к типу F. Разрядность этого типа определяется конкретной реализацией CLI, которая, однако, должна гарантировать, что точность типа F не ниже, чем точность типа float64.
В-четвертых, типы-перечисления при копировании на стек вычислений автоматически превращаются в целые типы. Вообще, VES устроена таким образом, что типы-перечисления и целые типы являются совместимыми по присваиванию. Этим они отличаются от обычных типов-значений, которые при копировании на стек сохраняют свой тип и не совместимы с целыми типами.
И, наконец, для VES не имеет значения, какой точный тип имеют управляемые указатели и объектные ссылки. Любой управляемый указатель считается имеющим тип , а любая объектная ссылка представляется типом O. Это означает, что согласно спецификации CLI система выполнения не обязана отслеживать правильность типов управляемых указателей и объектных ссылок. Действительно, контроль за правильностью типов находится в компетенции верификатора.
Для хранения локальных переменных и параметров метода используются два массива, которые, как и стек вычислений, состоят из слотов. При этом каждой переменной и каждому параметру соответствует ровно один слот.
Для доступа к локальным переменным и параметрам используются их индексы в массивах переменных и параметров. При этом нумерация осуществляется с нуля.
Если один и тот же слот стека вычислений в разные моменты времени может содержать данные разных типов, то слоты, используемые для хранения переменных и параметров, строго типизированы, но зато поддерживают все типы, определенные в спецификации
Слоты, из которых состоят массивы переменных и параметров, адресуемы. Это означает, что в языке CIL существуют специальные инструкции, позволяющие получить адрес локальной переменной или параметра метода в виде управляемого указателя.
Компилятор, генерирующий CIL-код, не должен делать никаких предположений о том, как переменные и параметры размещены в памяти. Дело в том, что реализации CLI могут любым образом переупорядочивать переменные и параметры, могут произвольно выравнивать их в памяти и даже использовать для их хранения регистры процессора.
Область локальных данных является составной частью состояния метода и используется для размещения объектов, тип и/или размер которых неизвестен на этапе компиляции, но которые по тем или иным причинам нежелательно размещать в куче.
Память из области локальных данных может быть явно выделена с помощью инструкции localloc. Так как в языке CIL не существует инструкции, освобождающей память в области локальных данных, то эту область невозможно использовать для реализации менеджера памяти общего назначения.
Область локальных данных существует ровно столько, сколько исполняется метод, состоянию которого она принадлежит. После прекращения работы метода она автоматически освобождается.
В верифицированном коде использование области локальных данных запрещено.
Одной из основных особенностей платформы .NET, делающих ее привлекательной для разработки приложений, является механизм автоматического управления памятью, известный как сборка мусора (
Спецификация CLI утверждает, что память для объектов, используемых в программе, выделяется в управляемой куче (managed
В .NET реализован так называемый сборщик мусора с поколениями (generational
Под управляемую кучу резервируется непрерывная область
Для некоторых объектов определены методы Finalize, выполняющие некие действия при удалении объекта из кучи. Эти методы являются аналогами деструкторов языка C++ и используются главным образом для освобождения системных ресурсов, связанных с объектами. В целях повышения эффективности сборщика мусора при выделении памяти для объекта, имеющего метод Finalize, адрес этого объекта заносится в список завершения (finalization list).
Если сравнить механизм выделения памяти в управляемой куче .NET с работой функции malloc языка C, можно прийти к выводу, что функция malloc работает гораздо менее эффективно. Причина в том, что исполняющая среда языка C организует кучу в виде связного списка блоков памяти. При этом размеры блоков в общем случае различны. Функции malloc приходится выполнять поиск свободного блока нужного размера, разбивать этот блок и затем вносить необходимые изменения в список блоков. Ясно, что выполнение этих действий требует значительно больше времени, чем простое увеличение указателя HeapPtr.
Перед тем как приступить к описанию алгоритма сборки мусора в .NET, необходимо сделать важное замечание, касающееся уровня абстракции, на котором мы будем рассматривать этот вопрос. Дело в том, что нам придется отрешиться от понятий, с которыми оперирует спецификация CLI, потому что сборщик мусора относится не к спецификации, а к конкретной реализации. Другими словами, мы не можем обсуждать сборщик мусора в терминах виртуальной системы выполнения. Вместо этого нам придется перейти на уровень конкретной системы выполнения, имеющей следующие особенности:
Ключевую роль в работе сборщика мусора играет понятие корень (root). Корнем считается указатель на объект кучи, расположенный вне кучи. Таким образом, корнями являются глобальные переменные,
Работа сборщика мусора основана на предположении, что объекты, непосредственно или транзитивно достижимые из корней, нужно сохранить в куче, так как они могут использоваться в программе. Все остальные объекты можно удалить.
Запуск сборщика мусора осуществляется в тот момент, когда совокупный размер объектов в куче достигает некоторой границы. При этом все потоки, запущенные приложением, приостанавливаются до завершения сборки мусора.
Для каждой точки в коде программы сборщик мусора может эффективно определить набор корней благодаря специальной таблице корней. Эта таблица строится JIT-компилятором, и в ней каждому адресу в коде каждого метода сопоставляется набор тех регистров процессора, локальных переменных и параметров этого метода, которые являются корнями. При этом локальные переменные и параметры задаются своими смещениями относительно фрейма. Таким образом, для определения полного набора корней сборщику мусора достаточно выполнить следующие операции:
После вычисления полного набора корней сборщик мусора строит граф достижимости объектов кучи. Входами в граф служат объекты, на которые указывают корни. Поля этих объектов, имеющие ссылочные типы, сканируются, и в граф добавляются объекты, на которые эти поля указывают. Затем сканируются поля этих добавленных объектов. Процесс построения графа продолжается до тех пор, пока в него не войдут все объекты, достижимые из корней. При этом информацию о полях объектов сборщик мусора берет из метаданных, и ни один объект не рассматривается дважды.
Среди объектов, не попавших в граф достижимости, сборщик мусора ищет такие объекты, адреса которых записаны в список завершения. Эти адреса добавляются в очередь завершения, а сами объекты считаются достижимыми и не подлежащими удалению. Методы Finalize объектов, попавших в очередь завершения, выполняются затем в отдельном потоке.
Нетрудно сообразить, что эти объекты могут быть удалены только при следующей сборке мусора, и, более того, только при условии, что их методы Finalize успели выполниться и не привели к тому, чтобы объекты стали достижимыми из корней.
После того как определены все достижимые объекты, сборщик мусора выполняет
Проведение сборки мусора только для части объектов кучи позволяет существенно сократить время работы сборщика. Поэтому все объекты делятся на три категории, называемые поколениями. В поколении 0 сборка мусора проводится чаще всего. Объекты, пережившие сборку мусора в поколении 0, переводятся в поколение 1, в котором сборка мусора осуществляется реже. Объекты, не удаленные после сборки мусора в поколении 1, переводятся в поколение 2. Сборка мусора в поколении 2 выполняется совсем редко.
Эффективность организации сборки мусора с поколениями обосновывается тем, что молодые объекты имеют меньшее время жизни. Это утверждение получено эмпирическим путем и справедливо для подавляющего большинства реальных приложений.
Еще одним способом увеличения производительности сборщика мусора является выделение отдельной кучи для больших объектов. Большими считаются объекты, размер которых превышает 85000 байт. Куча больших объектов никогда не дефрагментируется, и все объекты в ней считаются принадлежащими поколению 2.
Виртуальная система выполнения (Virtual Execution System - VES) представляет собой абстрактную виртуальную машину, способную выполнять управляемый код. Можно сказать, что виртуальная система выполнения существует только "на бумаге", потому что ни одна из реализаций CLI не содержит интерпретатора CIL-кода (вместо этого используется JIT-компилятор, транслирующий инструкции CIL в команды процессора). Другими словами, виртуальная система выполнения не зря называется виртуальной (то есть мнимой), ее предназначение - служить образцом, которому должна соответствовать любая реализация CLI. Какую бы технологию ни использовала эта реализация для выполнения программ, эта технология должна работать так же, как работала бы виртуальная система выполнения.
Если сравнить CLI с ее ближайшим конкурентом - платформой Java, можно прийти к выводу, что VES является значительно более абстрактной моделью, чем виртуальная машина Java (
Изучение работы виртуальной машины CLI заключается в том, чтобы понять, что представляет собой состояние виртуальной машины и как это состояние меняется во времени. В этом разделе мы не будем затрагивать вопрос изменения состояния, так как оно связано с выполнением инструкций языка CIL, разговор о котором мы отложим до третьей главы нашего учебника.
На рис. 1.9 показана схема состояния виртуальной машины, из которой видно, что виртуальная машина может выполнять сразу несколько нитей (threads). Как уже говорилось ранее, виртуальная машина является всего лишь моделью поведения конкретных реализаций CLI, поэтому мы будем предполагать, что все нити выполняются параллельно. На самом деле, нити могут работать как параллельно, так и в режиме вытесняющей многозадачности, могут отображаться на процессы или на нити операционной системы, а могут и не отображаться. Сейчас для нас такие детали не имеют значения.
(рис 1.9) Состояние виртуальной машины
Состояние виртуальной машины является совокупностью состояний нитей и состояния кучи.
Состояние нити представляет собой
Такая схема состояния нити является абстракцией традиционной модели последовательности вызовов методов. Традиционная модель предполагает наличие для каждой нити единого стека, в котором для каждого вызванного метода размещаются его параметры, локальные переменные, а также служебная информация, необходимая для обеспечения возврата управления в вызвавший метод. Разработчики спецификации CLI сознательно отказались от использования в явном виде единого
Состояние кучи определяется состояниями содержащихся в ней объектов. Спецификация VES содержит упоминание о том, что допустимо существование сразу нескольких куч (например, реализация CLI может ранжировать объекты по размеру и использовать для их хранения разные кучи). Однако на рис. 1.9 изображена только одна куча, так как, по нашему мнению, количество куч определяется конкретными реализациями CLI и для понимания работы VES не существенно.
Спецификация VES предполагает, что для удаления ненужных объектов из кучи будет использоваться какой-либо алгоритм автоматического управления памятью. При этом детали такого алгоритма не рассматриваются, то есть в реализациях CLI могут применяться различные алгоритмы сборки мусора.
В заключение необходимо отметить, что состояния нитей и состояние кучи должны находиться в
На рис. 1.10 изображена схема состояния метода. Элементы состояния метода можно условно разделить на две группы: изменяемые данные и неизменяемые данные. Изменяемые данные доступны из тела метода для чтения и записи, в то время как неизменяемые данные либо доступны только для чтения либо вообще предназначены для внутреннего использования в системе выполнения.
(рис 1.10) Состояние метода
Давайте перечислим элементы состояния метода, входящие в группу изменяемых данных:
Содержит адрес следующей инструкции в
Виртуальная система выполнения работает по принципу стекового процессора. Это означает, что операнды инструкций, а также возвращаемые инструкциями значения хранятся в специальной области памяти, а именно на стеке вычислений.
Каждое состояние метода имеет собственный стек вычислений, содержимое которого сохраняется при вызове методов (то есть, если наш метод вызывает другой метод, то по завершении работы вызванного метода содержимое стека никуда не денется).
Для хранения локальных переменных в состоянии метода предусмотрена отдельная область памяти, состоящая из так называемых слотов (
Фактические параметры, переданные методу, записываются в специальную область памяти, которая организована так же, как и область локальных переменных.
В языке CIL предусмотрена инструкция localloc, которая позволяет динамически размещать объекты в области памяти, локальной для метода. Объекты в этой области живут до тех пор пока метод не завершится.
Обратите внимание, что стек вычислений, локальные переменные и параметры, а также локальные данные метода представляют собой логически отдельные области памяти. Каждая конкретная реализация CLI самостоятельно решает вопрос, где размещать эти области.
В группу неизменяемых данных входят следующие элементы состояния метода:
Содержит
Описатель метода доступен из кода метода, но в основном он используется системой выполнения при сборке мусора и обработке исключений.
Используется системой безопасности CLI и недоступен из кода метода.
Служит для организации списка состояний методов внутри системы выполнения и недоступно из кода метода. Фактически представляет собой указатель на состояние метода, из тела которого был вызван текущий метод.
Итак, несмотря на то, что большинство современных процессоров для организации вычислений используют регистры, в виртуальной системе выполнения вместо регистров применяется стек вычислений. Это связано, скорее всего, с тем, что стековые вычисления достаточно легко можно отобразить на регистры процессора, так как модель, использующая стек, более абстрактна, чем регистровая модель.
Стек вычислений в VES состоит из слотов. При этом глубина стека (максимальное количество слотов) всегда ограничена и задается статически в заголовке метода. Решение ограничить глубину стека было принято разработчиками спецификации CLI для того, чтобы облегчить создание JIT-компиляторов.
На входе метода стек вычислений всегда пуст. Затем он используется для передачи операндов инструкциям CIL, для передачи фактических параметров вызываемым методам, а также для получения результатов выполнения инструкций и вызываемых методов. Если метод возвращает какое-то значение, то оно кладется на стек вычислений перед завершением метода.
Важной особенностью организации стека вычислений является то обстоятельство, что его слоты не адресуются, то есть мы не можем получить указатель на какой-либо слот и записать в него значение.
Каждый слот стека вычислений может содержать ровно одно значение одного из следующих типов:
int64 - 8-байтовое целое со знаком;int32 - 4-байтовое целое со знаком;native int - знаковое целое, разрядность которого зависит от аппаратной платформы (может быть 4 или 8 байт);F - число с плавающей точкой, разрядность которого зависит от аппаратной платформы (не может быть меньше 8 байт); - управляемый указатель;O - объектная ссылка;Таким образом, слоты стека вычислений могут иметь различный размер в зависимости от типов записанных в них значений.
Также мы можем видеть, что допустимые типы значений для стека вычислений не совпадают с общей системой типов F стека вычислений не имеет аналога в и O соответственно.
Давайте обсудим, как в VES осуществляется работа с типами данных, не поддерживаемыми напрямую стеком вычислений.
Во-первых, короткие целые типы ( bool, char, int8, int16, unsigned int8, unsigned int16 ) при загрузке на стек вычислений расширяются до int32. При этом знаковые короткие целые типы ( int8, int16 ) расширяются с сохранением знака, а беззнаковые расширяются путем добавления нулевых битов. При сохранении значения со стека вычислений в переменной, параметре, поле объекта или элементе массива происходит обратное сужающее преобразование.
Во-вторых, беззнаковый тип unsigned int32 при загрузке на стек вычислений становится знаковым int32, и аналогично, беззнаковый unsigned int64 становится знаковым int64. При этом, естественно, никаких преобразований не происходит - просто последовательность бит, которая раньше считалась беззнаковым целым, копируется на стек вычислений. Вообще говоря, утверждение, что целые типы int32, int64 и native int на стеке вычислений имеют знак, достаточно спорно. Правильнее было бы сказать, что они могут представлять как знаковые, так и беззнаковые целые числа в зависимости от того, какие инструкции CIL используются для их обработки.
В-третьих, типы float32 и float64 при копировании на стек вычислений преобразуются к типу F. Разрядность этого типа определяется конкретной реализацией CLI, которая, однако, должна гарантировать, что точность типа F не ниже, чем точность типа float64.
В-четвертых, типы-перечисления при копировании на стек вычислений автоматически превращаются в целые типы. Вообще, VES устроена таким образом, что типы-перечисления и целые типы являются совместимыми по присваиванию. Этим они отличаются от обычных типов-значений, которые при копировании на стек сохраняют свой тип и не совместимы с целыми типами.
И, наконец, для VES не имеет значения, какой точный тип имеют управляемые указатели и объектные ссылки. Любой управляемый указатель считается имеющим тип , а любая объектная ссылка представляется типом O. Это означает, что согласно спецификации CLI система выполнения не обязана отслеживать правильность типов управляемых указателей и объектных ссылок. Действительно, контроль за правильностью типов находится в компетенции верификатора.
Для хранения локальных переменных и параметров метода используются два массива, которые, как и стек вычислений, состоят из слотов. При этом каждой переменной и каждому параметру соответствует ровно один слот.
Для доступа к локальным переменным и параметрам используются их индексы в массивах переменных и параметров. При этом нумерация осуществляется с нуля.
Если один и тот же слот стека вычислений в разные моменты времени может содержать данные разных типов, то слоты, используемые для хранения переменных и параметров, строго типизированы, но зато поддерживают все типы, определенные в спецификации
Слоты, из которых состоят массивы переменных и параметров, адресуемы. Это означает, что в языке CIL существуют специальные инструкции, позволяющие получить адрес локальной переменной или параметра метода в виде управляемого указателя.
Компилятор, генерирующий CIL-код, не должен делать никаких предположений о том, как переменные и параметры размещены в памяти. Дело в том, что реализации CLI могут любым образом переупорядочивать переменные и параметры, могут произвольно выравнивать их в памяти и даже использовать для их хранения регистры процессора.
Область локальных данных является составной частью состояния метода и используется для размещения объектов, тип и/или размер которых неизвестен на этапе компиляции, но которые по тем или иным причинам нежелательно размещать в куче.
Память из области локальных данных может быть явно выделена с помощью инструкции localloc. Так как в языке CIL не существует инструкции, освобождающей память в области локальных данных, то эту область невозможно использовать для реализации менеджера памяти общего назначения.
Область локальных данных существует ровно столько, сколько исполняется метод, состоянию которого она принадлежит. После прекращения работы метода она автоматически освобождается.
В верифицированном коде использование области локальных данных запрещено.
Одной из основных особенностей платформы .NET, делающих ее привлекательной для разработки приложений, является механизм автоматического управления памятью, известный как сборка мусора (
Спецификация CLI утверждает, что память для объектов, используемых в программе, выделяется в управляемой куче (managed
В .NET реализован так называемый сборщик мусора с поколениями (generational
Под управляемую кучу резервируется непрерывная область
Для некоторых объектов определены методы Finalize, выполняющие некие действия при удалении объекта из кучи. Эти методы являются аналогами деструкторов языка C++ и используются главным образом для освобождения системных ресурсов, связанных с объектами. В целях повышения эффективности сборщика мусора при выделении памяти для объекта, имеющего метод Finalize, адрес этого объекта заносится в список завершения (finalization list).
Если сравнить механизм выделения памяти в управляемой куче .NET с работой функции malloc языка C, можно прийти к выводу, что функция malloc работает гораздо менее эффективно. Причина в том, что исполняющая среда языка C организует кучу в виде связного списка блоков памяти. При этом размеры блоков в общем случае различны. Функции malloc приходится выполнять поиск свободного блока нужного размера, разбивать этот блок и затем вносить необходимые изменения в список блоков. Ясно, что выполнение этих действий требует значительно больше времени, чем простое увеличение указателя HeapPtr.
Перед тем как приступить к описанию алгоритма сборки мусора в .NET, необходимо сделать важное замечание, касающееся уровня абстракции, на котором мы будем рассматривать этот вопрос. Дело в том, что нам придется отрешиться от понятий, с которыми оперирует спецификация CLI, потому что сборщик мусора относится не к спецификации, а к конкретной реализации. Другими словами, мы не можем обсуждать сборщик мусора в терминах виртуальной системы выполнения. Вместо этого нам придется перейти на уровень конкретной системы выполнения, имеющей следующие особенности:
Ключевую роль в работе сборщика мусора играет понятие корень (root). Корнем считается указатель на объект кучи, расположенный вне кучи. Таким образом, корнями являются глобальные переменные,
Работа сборщика мусора основана на предположении, что объекты, непосредственно или транзитивно достижимые из корней, нужно сохранить в куче, так как они могут использоваться в программе. Все остальные объекты можно удалить.
Запуск сборщика мусора осуществляется в тот момент, когда совокупный размер объектов в куче достигает некоторой границы. При этом все потоки, запущенные приложением, приостанавливаются до завершения сборки мусора.
Для каждой точки в коде программы сборщик мусора может эффективно определить набор корней благодаря специальной таблице корней. Эта таблица строится JIT-компилятором, и в ней каждому адресу в коде каждого метода сопоставляется набор тех регистров процессора, локальных переменных и параметров этого метода, которые являются корнями. При этом локальные переменные и параметры задаются своими смещениями относительно фрейма. Таким образом, для определения полного набора корней сборщику мусора достаточно выполнить следующие операции:
После вычисления полного набора корней сборщик мусора строит граф достижимости объектов кучи. Входами в граф служат объекты, на которые указывают корни. Поля этих объектов, имеющие ссылочные типы, сканируются, и в граф добавляются объекты, на которые эти поля указывают. Затем сканируются поля этих добавленных объектов. Процесс построения графа продолжается до тех пор, пока в него не войдут все объекты, достижимые из корней. При этом информацию о полях объектов сборщик мусора берет из метаданных, и ни один объект не рассматривается дважды.
Среди объектов, не попавших в граф достижимости, сборщик мусора ищет такие объекты, адреса которых записаны в список завершения. Эти адреса добавляются в очередь завершения, а сами объекты считаются достижимыми и не подлежащими удалению. Методы Finalize объектов, попавших в очередь завершения, выполняются затем в отдельном потоке.
Нетрудно сообразить, что эти объекты могут быть удалены только при следующей сборке мусора, и, более того, только при условии, что их методы Finalize успели выполниться и не привели к тому, чтобы объекты стали достижимыми из корней.
После того как определены все достижимые объекты, сборщик мусора выполняет
Проведение сборки мусора только для части объектов кучи позволяет существенно сократить время работы сборщика. Поэтому все объекты делятся на три категории, называемые поколениями. В поколении 0 сборка мусора проводится чаще всего. Объекты, пережившие сборку мусора в поколении 0, переводятся в поколение 1, в котором сборка мусора осуществляется реже. Объекты, не удаленные после сборки мусора в поколении 1, переводятся в поколение 2. Сборка мусора в поколении 2 выполняется совсем редко.
Эффективность организации сборки мусора с поколениями обосновывается тем, что молодые объекты имеют меньшее время жизни. Это утверждение получено эмпирическим путем и справедливо для подавляющего большинства реальных приложений.
Еще одним способом увеличения производительности сборщика мусора является выделение отдельной кучи для больших объектов. Большими считаются объекты, размер которых превышает 85000 байт. Куча больших объектов никогда не дефрагментируется, и все объекты в ней считаются принадлежащими поколению 2.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.