Вынесение полезной отладочной и проектной информации в необязательные компоненты
Управляемый код может содержать большое количество информации, которая может быть полезной для разработчиков при отладке каркасов, компонентов и приложений. В качестве примера подобных данных, привносящих добавленную стоимость, можно привести текстовые описания возможных исключений, которые возникают в процессе выполнения кода. Одно дело, когда сообщения об ошибках выводятся в виде малоинформативных фраз наподобие "Неизвестное исключение" или "Возбуждено исключение System.IO.SomeRandomException", и совершенно другое, когда вы получаете понятное простому смертному сообщение, подробно описывающее суть происшедшего и его наиболее вероятную причину. Информация подобного рода полезна на стадиях тестирования и отладки, но при разработке таких крупных проектов, как NET Compact Framework, для хранения всех строк сообщений об ошибках может потребоваться слишком много места, что в случае мобильных устройств является непозволительной роскошью.
.NET Framework для настольных компьютеров просто включает в себя обширную информацию об исключениях наряду с другими ресурсами. Эта информация доступна не только на английском, но и на многих других основных языках, для которых была выполнена локализация Visual Studio; это означает, что конечные пользователи имеют возможность получать богатую диагностическую информацию на своем родном языке. Чтобы обеспечить доступ к этой информации программным путем, каждое исключение управляемого кода имеет свойство .Message, открывающее программный доступ к соответствующему тексту.
Наличие текстовых строк с богатой отладочной информацией является несомненным преимуществом, однако за это преимущество приходится платить увеличением размера системы. Изначально ясно, что для подробных текстовых описаний требуется выделять много места. Для этого потребуется еще больше места, если предусмотрена локализация описаний. В связи с этим возникает вопрос о возможных проектных ограничениях, поскольку задачи обеспечения компактности системы и предоставления как можно более подробной текстовой информации вступают в конфликт между собой.
В .NET Compact Framework эта проблема решается за счет разбиения указанной информации на отдельные вспомогательные компоненты не входящие в базовый состав NET Compact Framework. Эти вспомогательные компоненты могут устанавливаться поверх .NET Compact Framework в соответствии с необходимостью или желанием. Файл, содержащий эту информацию, имеет название System.SR.dll, а его размер составляет 91 Кбайт. Для других языков локализации предусмотрены дополнительные версии этого файла. Всего в настоящее время существует 10 таких локализованных языковых версий, суммарный размер которых достигает 900 Кбайт, что составляет ощутимую долю общего размера .NET Compact Framework. Таким образом, исключение этих строк из .NET Compact Framework позволяет значительно уменьшить суммарный размер системы.
При установке .NET Compact Framework в ОЗУ или ПЗУ мобильных устройств указанные вспомогательные файлы, как правило, не устанавливаются; обычно их устанавливают лишь тогда, когда это необходимо или желательно. В среде разработки Visual Studio .NET эти файлы автоматически устанавливаются во время отладки, поскольку их присутствие почти всегда является желательным для разработчиков
Если во время выполнения возбуждается исключение, .NET Compact Framework проверяет, присутствует ли на устройстве соответствующая языковая версия файла с диагностическими текстами исключений. Если это так, то осуществляется доступ к нужному файлу и загружается соответствующее текстовое описание. В противном случае NET Compact Framework пытается найти языково-инвариантную (английскую) версию ресурсного файла со строками описаний исключений. Если этот файл присутствует, выполняется загрузка текста. Если же файл отсутствует, отображается заданный по умолчанию текст, в котором ошибка не конкретизируется (хотя программное имя исключения и может дать некоторую информацию о ее природе).
Использование механизма необязательных вспомогательных компонентов в .NET Compact Framework позволило достичь разумного баланса между максимально возможным уменьшением размера системы и предоставлением разработчикам как можно более полной отладочной информации
Аналогичная стратегия проектирования применяется и в тех случаях, когда компоненты могут предоставить на стадии проектирования некую дополнительную информацию, которая не потребуется во время выполнения. В процессе создания пользовательских элементов управления можно создать отдельные версии компонентов для стадии проектирования и времени выполнения, различающиеся тем, что компоненты, используемые на стадии проектирования, содержат дополнительные метаданные, интерпретация которых на стадии разработки предоставляет расширенную информацию, позволяющую принимать более эффективные проектные решения
Аналогичные модели, предусматривающие создание необязательных файлов ресурсов или отдельных версий компонентов для стадии проектирования, вы можете применять и в своей практике проектирования приложений, если это может привести к существенному уменьшению размера приложения.
Средства подключения к базам данных SQL СЕ/SQL
Необязательные компоненты другой разновидности, которые могут устанавливаться поверх .NET Compact Framework, обеспечивают связь с определенными базами данных. Когда поставлялась версия 1.1 .NET Compact Framework, одновременно с ней поставлялись ее необязательные компоненты, обеспечивающие доступ к локальным базам данных SQL СЕ и удаленным базам данных SQL Server. Не являясь частью базового состава .NET Compact Framework, эти компоненты оказываются весьма полезными при создании приложений, работающих с упомянутыми базами данных. Существуют также аналогичные компоненты, обеспечивающие доступ к базам данных сторонних разработчиков.
Элементы, отсутствующие в первой версии .NET Compact Framework
Защита доступа
Концепция
Средства безопасности доступа кода обеспечивают возможность управления предварительно определяемыми политиками, разрешающими выполнение кода на основании подтверждений, предоставляемых самим кодом. Такими подтверждениями могут, например, служить криптографическая подпись, подтверждающая подлинность издателя компонента, полное имя самого компонента, местоположение установки компонента в локальной файловой системе или URL-адрес, с которого был загружен компонент. На основании предоставленного подтверждения и локальной политики, определенной для той машины, на которой должен выполняться код, коду предоставляются те или иные полномочия. В качестве примера можно привести следующие возможные уровни полномочий:
В варианте .NET Framework, ориентированном на настольные компьютеры и серверы, доступны не только эти, но и многие другие разрешения, которые могут определяться в качестве политик. По данному вопросу имеется обширная литература и документация.
.NET Compact Framework проектировалась с учетом поддержки описанной модели безопасности, основанной на политиках. Однако в первом выпуске .NET Compact Framework политика безопасности определялась очень просто: "Весь код пользуется полными доверительными отношениями". Это означает, что приложения, предназначенные для .NET Compact Framework v1.1, выполняющейся на устройстве, будут выполняться в этой среде с тем же набором разрешений, с которым на устройстве выполняются собственные коды.
Решение о закреплении разрешенных полномочий на уровне "выполнения в условиях полных доверительных отношений" для первого выпуска .NET Compact Framework было продиктовано исключительно прагматическими соображениями. В ходе обсуждений, сопровождавших ранние опыты внедрения данного продукта, стало очевидным, что первая волна конкурентоспособных приложений для мобильных устройств будет, скорее всего, представлена приложениями, которые явным образом устанавливаются на устройствах пользователями или администраторами, а не загружаются и выполняются на лету. Подобные виды приложений начали уже в заметной степени вытеснять традиционные приложения для устройств на основе собственных кодов, и поэтому использование аналогичной модели полномочий вполне соответствовало сложившейся ситуации. Если бы мы располагали неограниченным временем для проектирования, построения, тестирования и окончательного уточнения политик безопасности на основании откликов пользователей, то модель, используемая в первой версии, была бы проработана более тщательно. Однако в результате многочисленных дискуссий мы поняли, что не это требование будет определять успешность выполнения приложений в нашей первой версии продукта. В силу этого создание более совершенного механизма политик безопасности было отложено до следующего выпуска, что дало возможность сконцентрировать усилия на разработке других средств, вошедших в первую версию. Это решение далось нелегко, однако, по прошествии уже достаточно большого времени, мы по-прежнему убеждены в его правильности. Следует отметить, что на некоторых из устройств, пользующихся массовым спросом, например, на некоторых смартфонах, эксплуатируются политики безопасности, требующие, чтобы приложения, основанные на собственных кодах, были снабжены криптографическими подписями; в случае таких устройств в отношении приложений, основанных на управляемом коде, действуют те же политики.
В дальнейшем желательно внедрить модель безопасности, поддерживающую загрузку кода на лету и присвоение полномочий на основе подтверждений, предоставляемых кодом. Поэтому весьма вероятно, что в будущих версиях .NET Compact Framework будет предусмотрено несколько различных уровней доверительности и полномочий, охватывающих интервал от "полного доверия" на основе предоставляемых подтверждений до весьма ограниченных возможностей для кода, пользующегося низким уровнем доверительности. Кроме того, различные типы мобильных устройств будут, вероятно, характеризоваться заданием на них различных политик. На некоторых устройствах политики будут определяться конечными пользователями, на других — администраторами, изготовителями оборудования или же сетевыми операторами, обслуживающими то или иное устройство. Поддержка этих возможностей будет осуществляться встроенной моделью обеспечения безопасности доступа.
Мультимедиа
Еще одной областью, от явной поддержки которой в первом выпуске .NET Compact Framework мы решили отказаться, является работа с мультимедийной информацией. Это, например, означает, что разработчики, желающие воспроизводить звуки или показывать видео, должны использовать для этой цели собственные коды. Как и в предыдущем случае, если бы для разработки, тестирования и доработки этих средств у нас было больше времени, то их поддержка была бы обеспечена уже в первом выпуске .NET Compact Framework. Анализ отзывов, полученных нами от лиц, проводивших первые испытания этого продукта, позволил сделать вывод, что применительно к тем видам приложений, в создании которых в настоящее время существует потребность, поддержка данных возможностей не являлась критическим фактором успешности. По всей вероятности, в будущих версиях .NET Compact Framework поддержка мультимедийной информации будут более интенсивной.
Оглядываясь назад, мы можем констатировать, что принятое нами решение было правильным.
Как запускается и выполняется код
При первоначальном запуске приложения на основе управляемого кода загрузка, верификация и запуск кода осуществляются за несколько шагов. Когда такое приложение уже запущено и выполняется, среда выполнения управляемого кода продолжает играть важную роль во время загрузки новых классов и JIT-компиляции кода, когда это оказывается необходимым, а также в процессе управления памятью, отводимой для приложения. Полезно иметь более или менее полное общее представление о том, как все это происходит. Процесс запуска и выполнения приложения можно пояснить при помощи описанных ниже шести шагов:
1
2.
3.
4.
5.
6.
На первый взгляд, вышеперечисленные действия требуют выполнения большого объема работы, однако в действительности все происходит очень быстро.
На заметку! "Типом" являются любые данные, с которыми может работать программист. Это можно сформулировать и иначе, сказав, что типами являются любые типы данных и классы. Все является типом, включая целые и десятичные числа, другие скалярные величины, строки и классы. Любая "сущность", которой вы можете манипулировать внутри кода, является экземпляром типа. Классы являются специальными типами в том смысле, что они содержат не только данные, но и код, и поддерживают такие объектно-ориентированные свойства, как наследование.
На заметку! После загрузки определений классов и других типов в память, их верификации и компиляции эти данные размещаются в распределенной памяти точно так же, как и любые другие данные, которые разработчики распределяют в своих приложениях. Рассмотрим пример. Предположим, вы создаете объект ArrayList:
System.Collection.ArrayList aList = new System.Collection.ArrayList();
При этом выполняются следующие операции:
1. Вышеприведенный код представляется в вашем приложении на промежуточном языке IL. Этот IL-код подвергается JIT-компиляции во время выполнения непосредственно перед тем, как выполняться в первый раз. Этот код помещается в распределенную память.
2. По окончании JIT-компиляции кода исполнительный механизм пытается определить, загружена ли информация о типе System.Collections.Array в память; если это не так, выполняется распределение памяти для этого типа, а также загрузка и связывание определения с классом ArrayList.
3. Во время выполнения приведенного выше фактического кода должен быть выполнен конструктор объекта ArrayList. Если соответствующий код еще не загружался и не подвергался JIT-компиляции, осуществляется распределение памяти для кода конструктора и JIT-компиляция этого кода. Выполняется загрузка кода, его верификация и JIT-компиляция, и память, распределенная для кода этого конструктора, связывается с конструктором класса ArrayList.
То же самое относится и к любому другому загружаемому типу или вызываемому методу; если необходимая работа по их загрузке не была до этого выполнена, они загружаются и компилируются по мере необходимости, и используемая для этого память связывается с типом.
Описанное распределение и отслеживание памяти играет очень важную роль. Как далее будет показано, к данным о коде и определениях классов, как и к любым другим объектам, могут применяться операции сборки мусора.
Управление памятью и сборка мусора
В процессе своего обычного выполнения ваш код периодически инициирует загрузку определений типов в память, компиляцию и выполнение кода, а также размещение объектов в памяти и их удаление из памяти. .NET Compact Framework проектировалась таким образом, чтобы обеспечить наилучший баланс между скоростью запуска, устойчивостью и непрерывностью выполнения кода даже в условиях существования жестких ограничений на объем доступной памяти. Для достижения этой цели загрузчик классов, механизм JIT-компиляции, а также модули распределения памяти и сборки мусора работают скоординированным образом.
Вероятнее всего, выполнение вашего кода будет сопровождаться периодическим созданием новых объектов и их последующим уничтожением после того, как необходимость в них исчезает. Эффективное удовлетворение запросов памяти для размещения объектов и ее освобождение достигаются в .NET Compact Framework за счет утилизации освобождаемой памяти путем так называемой "сборки мусора" (garbage collection). Операции по сборке мусора в той или иной форме используются в большинстве современных сред выполнения управляемого кода.
Сборка мусора в основном предназначена для решения двух задач: 1) восстановления памяти, которая больше не используется, и 2) уплотнения участков используемой памяти таким образом, чтобы для вновь распределяемых областей памяти были доступны как можно более крупные блоки. Тем самым решается одна из наиболее распространенных проблем, которая называется фрагментацией памяти. По своей сути эта проблема аналогична проблеме фрагментации дискового пространства. Если дисковое пространство периодически не реорганизовывать, то по прошествии некоторого времени оно дезорганизуется и разобьется на ряд чередующихся свободных и занятых участков небольшого размера; эти участки и являются фрагментами. Можно считать, что здесь мы имеем дело с проявлением действия закона увеличения энтропии применительно к дисковому пространству и памяти; для того, чтобы обратить энтропийные эффекты, необходимо затратить определенную работу. В результате фрагментации доступ к данным замедляется, поскольку в процессе чтения файлов головкам приходится многократно перемещаться вдоль поверхности диска. Фрагментация памяти оборачивается еще худшими бедами, так как для размещения объектов требуются достаточно большие непрерывные блоки памяти, способные уместить целиком все данные, относящиеся к объекту. После выполнения многочисленных операций выделения и освобождения порций памяти различного размера свободные области оказываются расположенными вразброс, а их размеры являются недостаточными, чтобы обеспечивалось наличие больших непрерывных участков памяти, необходимых для размещения объектов. Уплотнение всей используемой, но расположенной вразброс памяти позволяет создавать крупные блоки свободной памяти, которую можно эффективно использовать при распределении памяти для вновь создаваемых объектов.
Восстановление памяти является сравнительно несложным процессом. Выполнение кода временно прекращается, а активные (live) объекты (то есть объекты, к которым ваш код может осуществлять непосредственный или косвенный доступ) отыскиваются и рекурсивно помечаются. Так как остальные объекты, находящиеся в памяти, уже недоступны активному коду, они остаются непомеченными, что позволяет идентифицировать их как "мусор" и восстановить соответствующую память. Такая разновидность сборки мусора известна под названием "отслеживание и очистка" ("mark and sweep"). Обычно эта операция осуществляется довольно быстро.
Возможность уплотнения объектов в памяти является дополнительным преимуществом выполнения управляемого кода. В этом случае, в отличие от собственных кодов, все известные ссылки на ваши объекты известны и исполнительному механизму. Благодаря этому можно перемещать объекты из одного места памяти в другое, если в этом возникает потребность. Диспетчер памяти (исполнительный механизм) отслеживает, где именно находятся те или иные объекты, и перемещает их в случае необходимости. Это позволяет уплотнять расположение набора управляемых объектов, разбросанных в памяти.
Однако в рукаве у .NET Compact Framework наряду с JIT-компилятором, диспетчером памяти и сборщиком мусора спрятан еще один козырь: возможность очищать память от пассивного (dead) JIT-компилированного кода. Обычно это делать нежелательно, но при определенных обстоятельствах такая возможность становится незаменимой. Если уж исполнительный механизм, обеспечивая максимальную производительность, потрудился и выполнил JIT-компиляцию управляемого кода до собственных команд процессора, то отбрасывание JIT-компилированного кода могло бы показаться расточительством, поскольку когда в следующий раз возникнет необходимость в выполнении этого кода, то потребуется его повторная JIT-компиляция. В то же время, наличие такой возможности оборачивается благом в следующих двух случаях:
1. Когда приложение осуществило JIT-компиляцию и был выполнен большой объем кода, который в ближайшее время не будет вновь выполняться. Довольно часто ситуации подобного рода возникают в тех случаях, когда выполнение приложения происходит в несколько стадий. Вполне возможно, что код, запускаемый на начальной стадии выполнения приложения для инициализации или настройки объектов и данных, впоследствии повторно выполняться не будет. Если возникает необходимость в дополнительном распределении памяти, то имеется полный смысл использовать для этого память, в которой хранится данный код.
2. Когда набор хранящихся в памяти активных объектов становится настолько большим, что возникает риск аварийного завершения выполнения приложения, если для размещения дополнительных объектов, которые в соответствии с алгоритмом должны создаваться в ходе нормального выполнения приложения, не найдется достаточно большого объема свободной памяти. В этом случае исполнительный механизм будет вынужден вытеснять из памяти и периодически перекомпилировать необходимый приложению код лишь для того, чтобы приложение было в состоянии продолжать дальнейшее выполнение. Если другой возможной альтернативой является только прекращение выполнения приложения из-за нехватки памяти, то единственным решением может быть лишь очистка памяти от JIT-компилированного кода, даже если это означает дополнительные затраты времени на последующую повторную компиляцию этого кода.
Краткий обзор методов управления памятью и сборки мусора
Полезно разобраться в том, каким образом исполнительный механизм взаимодействует с кодом и объектами приложения в процессе его выполнения.
Приведенные ниже схематические диаграммы помогут нам проследить, как осуществляется управление памятью на различных стадиях выполнения приложения.
Рис. 3.2.
Таблица 3.1. Условные графические обозначения, используемые на рис. 3.2, 3.3 и остальных рисунках
| Фигура | Описание |
|---|---|
| Темно-серые овалы | Используемые в настоящее время объекты (например, объект Obj12 на рис. 3.2). |
| Белые овалы | Пассивные объекты. Эти объекты (например, объект Obj6 на рис. 3.2) являются "мусором", к которому у приложения больше нет доступа и от которого можно избавиться с целью высвобождения дополнительной памяти. |
| Темно-серые прямоугольники | Описания классов и типов, которые были загружены в память по той причине, что к типам осуществлял доступ выполняющийся код. |
| Внутренние прямоугольники | Код, содержащийся в классах. (Код целиком находится внутри класса.) |
| Темно-серые внутренние прямоугольники | Код, подвергнутый JIТ-компиляции, поскольку метод был вызван хотя бы один раз (например, код Code1 класса ClassXYZ на рис.3.2). |
| Светло-серые внутренние прямоугольники | Код, который еще не подвергался JIТ-компиляции. По сравнению с прямоугольниками JIТ-компилированного кода эти прямоугольники имеют меньшие размеры, ибо еще не скомпилированные методы (например, метод Code4 класса XYZ на рис. 3.2) занимают в памяти очень мало места. |
Приведенную на рис. 3.2 схему можно прокомментировать следующим образом:
■ Размеры отдельных элементов схемы выбраны таким образом, чтобы они давали представление об относительных объемах используемой памяти. Визуально большие классы и типы используют больше памяти по сравнению с визуально меньшими, поскольку содержат больше "начинки". Объекты различных типов используют различные объемы памяти. Пассивные объекты занимают память до тех пор, пока не будет выполнена операция сборки мусора. Методы, не подвергавшиеся JIТ-компиляции, занимают в памяти очень мало места.
■ В память загружаются описания всех типов и классов, которые используются в вашем приложении. Для различных типов и классов требуются различные объемы памяти.
■ JIT-компиляции подвергаются методы класса, которые вызывались хотя бы один раз. Пример: JIT-компиляции подвергались коды Code1, Code2, Code3 и Code7 класса ClassXYZ.
■ Те методы классов, которые еще не вызывались, не компилируются и поэтому не занимают много места в памяти. Пример: методы Code4 и Code5 класса XYZ еще не подвергались JIТ-компиляции. Как только они будут вызваны, будет осуществлена их JIT-компиляция и произведено распределение памяти для откомпилированного кода.
■ Объекты представляют собой экземпляры типов и классов и требуют места в памяти.
■ "Активные (live) объекты" — это объекты, к которым ваш код может получить доступ путем непосредственного или опосредованного использования глобальных, статических и стековых переменных.
■ "Пассивные (dead) объекты" — это объекты, доступ к которым со стороны вашего приложения стал уже невозможным, но которые еще не были удалены из памяти исполнительным механизмом. Таковыми на рис. 3.2 являются объекты Obj 3, Obj 6, Obj 9 и Obj11. До тех пор пока эти объекты не будут удалены, они будут занимать память подобно активным объектам или JIT-компилированному коду.
По мере того как объекты и другие типы, для которых память распределяется из кучи, будут создаваться вашим приложением и впоследствии становиться ненужными, наступит такой момент, когда невозможно будет создать никакой другой дополнительный объект, не удалив из памяти пассивные объекты. Как только это произойдет, исполнительный механизм инициирует сборку мусора. Состояние памяти приложения непосредственно перед выполнением операции сборки мусора показано на рис. 3.3.
По окончании сборки мусора становится возможным уплотнение активных объектов в памяти. Удаление пассивных объектов из памяти с последующим уплотнением активных объектов во многих случаях позволяет сформировать непрерывные свободные участки памяти большого размера, которые могут быть использованы при создании новых объектов. Состояние памяти приложения непосредственно после выполнения операций сборки мусора и уплотнения объектов показано на рис. 3.4.
Рис. 3.3.
Рис. 3.4.
В состоянии нормального устойчивого выполнения приложение периодически создает объекты и избавляется от них. В необходимых случаях исполнительный механизм осуществляет сборку мусора и уплотняет память, освобождая ее для размещения вновь создаваемых объектов. Типичное состояние памяти приложения в условиях сосуществования активных и пассивных объектов, а также JIT-компилированного и некомпилированного кода показано на рис. 3.5.
В случае нормально функционирующих приложений всегда имеется запас свободной памяти, достаточный для создания новых объектов и хранения ненужных, так что прибегать к сборке мусора приходится сравнительно редко. Объема областей памяти, освобождаемых в процессе сборки мусора, надолго хватает для того, чтобы приложение могло создавать новые объекты, не заботясь об удалении тех, необходимости в которых больше нет. Типичное состояние памяти приложения непосредственно после выполнения очередной операции сборки мусора представлено на рис. 3.6.
Иногда количество используемых активных объектов может возрастать настолько, что простое удаление пассивных объектов по типу "отслеживание и очистка" не может обеспечить освобождение достаточно большого объема памяти для того, чтобы исполнительный механизм мог создать и разместить в памяти новые объекты, которые требуются выполняющемуся коду приложения. В случае возникновения подобных ситуаций выполнение приложения должно быть прекращено из-за нехватки памяти. Состояние приложения, достигшего в ходе своего выполнения такого состояния, показано на рис. 3.7.
Рис. 3.5.
Рис. 3.6.
Рис. 3.7.
.NET Compact Framework справляется с подобными ситуациями, освобождая большие количества памяти, занимаемой текущим JIT-компилированным кодом. Может быть сброшен любой код, не выполняющийся в данный момент с использованием стека (или стеков, если речь идет о многопоточном выполнении). Это позволяет востребовать обратно память и использовать ее для нужд приложения, связанных с необходимостью размещения новых объектов или JIT-компиляции нового кода в случае, если метод ранее не выполнялся, но теперь вызывается. Состояние памяти приложения после отбрасывания ранее JIT-компилированного кода и сборки мусора показано на рис. 3.8.
Удаление JIT-компилированного кода является серьезным шагом, ибо часть этого кода придется JIT-компилировать повторно, но эта мера может оказаться весьма эффективной, если значительная часть ранее JIT-компилированного кода больше не потребуется для выполнения приложения. Это часто имеет место в тех случаях, когда заметная доля кода приходится на "начальный" код, используемый лишь для настройки последующего выполнения, или когда приложение разделяется на логические блоки, которые не обязательно должны выполняться все одновременно. Состояние памяти приложения вскоре после того, как отброшен весь возможный JIT-компилированный код, показано на рис. 3.9. Последующее размещение новых объектов и повторная JIT-компиляция кода методов осуществляются по мере необходимости.
Рис. 3.8.
Рис. 3.9.
Производительность может резко снизиться, если приложение продолжает размещать в памяти все новые и новые объекты, не освобождая ее от старых. Такая ситуация будет приводить к возникновению условий, при которых единственное, что удается удерживать в памяти, — это активные объекты и код, непосредственно указанные в стеке приложения (или стеках, если речь идет о многопоточном выполнении). В этом случае обычный код должен непрерывно подвергаться JIT-компиляции и освобождать память, когда необходимость в нем отпадает, поскольку для хранения JIT- компилированного кода доступна лишь небольшая часть памяти системы. Таким образом, системе приходится выполнять много лишней работы, то есть она, как принято говорить, "молотит впустую", поскольку и приложение, и среда времени выполнения работают на полную мощность с низкой эффективностью лишь для того, чтобы поддерживать возможность выполнения кода небольшими порциями. По мере асимптотического приближения приложения к этому состоянию его производительность резко падает. Возникновения таких состояний следует избегать! Пример приложения, оказавшегося в подобном состоянии, проиллюстрирован на рис. 3.10.
Рис. 3.10.
Резюме
.NET Compact Framework представляет собой богатую своими возможностями среду времени выполнения управляемого кода для мобильных устройств, пригодную для создания самых различных приложений, компонентов и каркасов приложений. Она с самого начала предназначалась для использования на устройствах, характеризующихся ограниченными ресурсами, но с самого начала проектировалась как совместимое на уровне двоичного кода подмножество платформы .NET Framework, ориентированной на настольные компьютеры и серверы. Подобно другим средам выполнения управляемого кода .NET Compact Framework включает в себя механизм выполнения собственных кодов и набор библиотек классов выполняющегося поверх него управляемого кода. Были применены стратегии проектирования, специально предназначенные для мобильных и встроенных устройств, что позволило обеспечить предоставление широкого подмножества функциональности платформы NET Framework и одновременно учесть специфику требований мобильных устройств в отношении размеров и производительности приложений.