CLR

Common Language Runtime (сокр. CLR) — исполняющая среда для управляемого кода, разработанная корпорацией Microsoft и являющаяся ключевым компонентом платформы .NET[3]. CLR представляет собой реализацию спецификации Common Language Infrastructure (CLI). Среда управляет запуском, безопасностью и жизненным циклом приложений, написанных на любых CLR-совместимых языках, таких как C#, Visual Basic .NET и F#[4][5].

Что важно знать
Common Language Runtime
англ. Common Language Runtime
Область использования Разработка программного обеспечения, Платформа .NET
Дата появления 13 февраля 2002
Место появления Редмонд, Вашингтон, США[1]
Автор понятия Microsoft[2]

Определение

Common Language Runtime реализует спецификацию Common Language Infrastructure (CLI) и функционирует как виртуальная машина, которая:

  • переводит промежуточный код (CIL/MSIL) в машинные инструкции с помощью JIT-компиляции;
  • управляет памятью и сборкой мусора;
  • обеспечивает обработку исключений и безопасность типов;
  • предоставляет единый доступ к библиотеке классов .NET (FCL/BCL);
  • поддерживает межъязыковую совместимость за счёт Common Type System и Common Language Specification[6].[7]

Основные функции CLR:

  1. автоматическое управление памятью (Garbage Collector);
  2. JIT-компиляция CIL-кода;
  3. централизованная обработка исключений;
  4. модель безопасности и верификация кода;
  5. типобезопасность;
  6. управление потоками исполнения;
  7. работа с метаданными и рефлексия.

История

Разработка CLR началась в корпорации Microsoft 13 июня 1998 года[8] в штаб-квартире компании в Редмонде, штат Вашингтон.

Версии для .NET Framework:

  • CLR 1.0 — выпущена вместе с .NET Framework 1.0 13 февраля 2002 года[9].
  • CLR 1.1 — выпущена 3 апреля 2003 года, позже интегрирована в Windows Server 2003[10].
  • CLR 2.0 — вышла 27 октября 2005 года и использовалась в .NET Framework версий 2.0, 3.0 и 3.5. Ключевые нововведения: обобщения (generics), полноценная поддержка 64-битных систем и значительные улучшения в работе сборщика мусора (GC)[11].
  • CLR 4.0 — появилась 12 апреля 2010 года и стала основой для всех версий .NET Framework 4.x. В этой версии были внедрены поддержка асинхронного программирования (async/await), новый JIT-компилятор RyuJIT, поддержка инструкций SIMD и обновлённые механизмы безопасности[12].

Версии для .NET Core и .NET (CoreCLR): Начиная с 2016 года, вместе с выпуском .NET Core, была представлена CoreCLR — открытая, кроссплатформенная реализация CLR. С выходом .NET 5 в 2020 году CoreCLR стала единой средой выполнения для всей платформы .NET, ориентированной на высокую производительность, облачные и микросервисные сценарии[13].

  • CLR в .NET 6 (ноябрь 2021) — получила значительные улучшения производительности за счёт динамической профильной оптимизации (Dynamic PGO), нового AOT-компилятора Crossgen2 и полноценной поддержки архитектуры ARM64 (включая Apple silicon)[14][15].
  • CLR в .NET 7 (ноябрь 2022) — ключевым нововведением стала технология On-Stack Replacement (OSR), позволяющая «на лету» заменять код долго выполняющихся методов на более оптимизированный. Также была представлена компиляция в нативный код (Native AOT) для консольных приложений и осуществлён переход сборщика мусора на использование регионов памяти вместо сегментов[16][17].
  • CLR в .NET 8 (ноябрь 2023, LTS) — динамическая PGO была включена по умолчанию, что обеспечило прирост производительности до 15 %. Добавлена поддержка векторных инструкций AVX-512, а Native AOT стал доступен для ASP.NET Core. Сборщик мусора получил функцию DATAS для динамической адаптации к нагрузке[18][19].
  • CLR в .NET 9 (ноябрь 2024) — продолжены улучшения PGO, ускорена обработка исключений, а функция DATAS в GC включена по умолчанию. JIT-компилятор научился размещать на стеке «упакованные» объекты (boxed objects), снижая нагрузку на GC. Появились переключатели функций (Feature Switches) для уменьшения размера приложений при AOT-компиляции[20][21].
  • CLR в .NET 10 (план: ноябрь 2025, LTS) — основной фокус сделан на снижении «цены абстракций» (Deabstraction) за счёт расширенного анализа выхода (Escape Analysis), который позволяет размещать на стеке массивы и делегаты. JIT-компилятор получил возможность размещать поля структур в регистрах процессора и девиртуализировать вызовы интерфейсов для массивов. Добавлена поддержка инструкций AVX10.2[22][23][24].

JIT-компилятор

JIT-компилятор (англ. Just-In-Time) преобразует промежуточный код CIL в машинный код непосредственно во время выполнения программы, как правило, при первом вызове метода, и кэширует результат для последующих вызовов.

Начиная с .NET Core, JIT-компилятор получил ряд значительных улучшений, направленных на повышение производительности:

  • Профильно-ориентированная оптимизация (PGO) — технология, представленная в .NET 6 и включённая по умолчанию в .NET 8, собирает данные о наиболее часто выполняемых участках кода («горячих путях»). Эти данные используются для более агрессивных оптимизаций, таких как девиртуализация и встраивание (inlining)[25]. В .NET 9 PGO была расширена для оптимизации проверок типов.
  • On-Stack Replacement (OSR) — появившаяся в .NET 7 функция, которая позволяет «на лету» заменять код долго выполняющегося метода на более оптимизированную версию, сгенерированную с учётом данных PGO, не дожидаясь следующего вызова.
  • Оптимизация структур — в .NET 8 была внедрена скалярная замена агрегатов (Struct Promotion), позволяющая JIT-компилятору рассматривать поля структур как отдельные локальные переменные для более эффективной оптимизации. В .NET 10 компилятор научился размещать поля структур напрямую в регистрах процессора.
  • Поддержка векторных инструкций — в .NET 8 добавлена поддержка AVX-512, а в .NET 10 — AVX10.2[26], что значительно ускоряет параллельную обработку данных.
  • Деабстракция — в .NET 10 ключевым направлением стало снижение «цены абстракций» за счёт расширенного анализа выхода (Escape Analysis). Это позволяет размещать на стеке объекты, которые ранее всегда выделялись в куче, например, небольшие массивы и делегаты, что снижает нагрузку на сборщик мусора.

Помимо JIT-компиляции, современные версии .NET активно развивают технологию Ahead-of-Time (AOT)-компиляции, которая преобразует CIL в машинный код на этапе сборки приложения:

  • Crossgen2 — представленный в .NET 6 инструмент выполняет предварительную компиляцию для ускорения запуска приложения, но оставляет JIT-компилятору возможность дополнительно оптимизировать код во время выполнения (например, с помощью PGO).
  • Native AOT — технология, полноценно представленная в .NET 7 для консольных приложений и расширенная на ASP.NET Core в .NET 8[27]. Она создаёт полностью самодостаточный исполняемый файл без зависимости от JIT-компилятора и установленной среды .NET, что обеспечивает минимальное время запуска и меньшее потребление памяти.

Сборщик мусора (GC)

Сборщик мусора (англ. Garbage Collector, GC) — компонент CLR, отвечающий за автоматическое управление памятью. Он освобождает память, занятую объектами, которые больше не используются приложением. GC в .NET использует модель с поколениями (0, 1 и 2) для оптимизации сборки недолговечных объектов, а также отдельную кучу для больших объектов (англ. Large Object Heap, LOH). Поддерживаются различные режимы работы, включая Workstation (для клиентских приложений) и Server (для серверных нагрузок с высокой пропускной способностью)[28].

Начиная с .NET Core, сборщик мусора получил ряд ключевых архитектурных улучшений, направленных на повышение производительности и эффективности:

  • Регионы памяти — в .NET 7 для 64-битных систем был завершён переход от сегментной модели кучи к региональной. Регионы — более мелкие и гибкие единицы памяти, что является основой для будущих оптимизаций и позволяет более эффективно управлять памятью. Эта работа велась, в том числе, в .NET 6, где были сокращены паузы GC[29].
  • Динамическая адаптация (DATAS) — представленная в .NET 8 и включённая по умолчанию в .NET 9 функция, которая позволяет GC динамически настраивать потребление памяти в зависимости от реальных потребностей приложения, а не только от общего лимита памяти.
  • Куча для закреплённых объектов (англ. Pinned Object Heap) — добавленная в .NET 8 специальная область памяти для закреплённых объектов. Это помогает уменьшить фрагментацию основной кучи, которая возникает, когда GC не может перемещать закреплённые объекты.
  • Снижение нагрузки на GC — в .NET 9 JIT-компилятор научился размещать на стеке «упакованные» объекты (англ. boxed objects), которые ранее всегда выделялись в куче, что снижает частоту сборок мусора. В .NET 10 была расширена возможность размещения на стеке массивов и делегатов.
  • Сокращение пауз на ARM64 — в .NET 10 для архитектуры ARM64 была внедрена новая схема барьера записи, которая сокращает паузы «stop-the-world» на 8–20%[30].

Загрузчик сборок

Ищет, проверяет и загружает .dll/.exe-файлы по требованию, разрешая зависимости[31].

Менеджер типов (CTS)

Гарантирует типобезопасность и единое представление данных для всех .NET-языков[31].

Система безопасности

В современных версиях .NET (начиная с .NET Core) модель безопасности CLR была кардинально пересмотрена. В отличие от .NET Framework, среда выполнения больше не использует технологию Code Access Security (CAS) и связанную с ней модель Security Transparency для создания «песочниц» внутри процесса[32][33]. Эти механизмы были упразднены из-за их сложности, низкой эффективности и несовместимости с кроссплатформенной архитектурой[34].

Современный подход к безопасности предполагает, что весь код, выполняемый внутри одного процесса, является полностью доверенным. Изоляция и ограничение прав достигаются на уровне операционной системы с помощью таких технологий, как контейнеризация (например, Docker), виртуализация или запуск приложений от имени пользователей с ограниченными привилегиями[35][36].

Метаданные

Содержат описание типов и членов, используемое всеми компонентами среды выполнения[37].

Жизненный цикл выполнения кода

  1. Компиляция исходного кода в CIL и метаданные.
  2. Загрузка сборок в домен приложения.
  3. Верификация и проверки безопасности.
  4. JIT-компиляция методов.
  5. Исполнение нативного кода под управлением CLR.
  6. Сборка мусора и выгрузка ресурсов[6].

Управление памятью и сборка мусора

Управляемая куча делится на поколения 0, 1, 2 и кучу для больших объектов (англ. Large Object Heap). Сборщик мусора применяет копирующий алгоритм для молодых объектов и уплотнение для поколения 2 (англ. Generation 2), поддерживает фоновые сборки, финализаторы и слабые ссылки[38].

Начиная с .NET Core, сборщик мусора получил ряд ключевых архитектурных улучшений, направленных на повышение производительности и эффективности. Основные направления развития включают переход на региональную модель кучи для более гибкого управления памятью, внедрение механизмов динамической адаптации к нагрузке (DATAS), уменьшение фрагментации за счёт отдельных куч для специфических объектов и снижение нагрузки на GC путём размещения большего числа объектов на стеке. Подробные сведения об этих изменениях представлены в разделе Сборщик мусора (GC).

Безопасность кода и верификация

Модель безопасности CLR претерпела кардинальные изменения с переходом от .NET Framework к .NET Core. Старые механизмы, основанные на попытках создать «песочницу» внутри процесса, были упразднены в пользу изоляции на уровне операционной системы. Основные компоненты и их текущий статус:

  • Code Access Security (CAS) — устаревшая модель безопасности из .NET Framework, позволявшая создавать внутрипроцессные «песочницы» и ограничивать действия кода на основе его происхождения (например, из интернета или локальной сети). С выходом .NET Core от CAS полностью отказались из-за её сложности, низкой эффективности и несовместимости с кроссплатформенной архитектурой. Начиная с .NET 5, связанные с ней API помечены как устаревшие и больше не поддерживаются.
  • Security Transparency (прозрачность безопасности) — компонент CAS, который разделял код на уровни доверия (transparent, safe-critical, critical)[39]. Как и CAS, эта модель была упразднена в .NET Core, и связанные с ней атрибуты больше не оказывают никакого эффекта на выполнение кода[40].
  • Strong Name (строгое имя) — технология, обеспечивающая уникальную идентичность сборки (имя, версия, открытый ключ). В .NET Framework она также использовалась для защиты от подмены и размещения сборок в GAC[41]. В современных версиях .NET среда выполнения больше не проверяет криптографическую подпись строгого имени при загрузке, и технология считается устаревшей[42]. Её основное применение сегодня — предотвращение конфликтов имён и обеспечение обратной совместимости. Важно отметить, что строгие имена не являются механизмом безопасности для аутентификации кода; для этих целей используется Authenticode[43].
  • Верификатор IL — компонент CLR, который остаётся актуальным. Он проверяет корректность и типобезопасность промежуточного кода CIL перед его JIT-компиляцией, что является фундаментальным элементом безопасности платформы, предотвращая некорректный доступ к памяти[44].

Взаимодействие с языками и платформами .NET

Межъязыковая интероперабельность опирается на:

  • Common Type System (CTS);
  • Common Language Specification (CLS);
  • метаданные сборок, позволяющие вызывать компоненты, написанные на C#, F#, VB.NET и др[45].

Преимущества

  • автоматическое управление памятью;
  • единая модель безопасности;
  • межъязыковая совместимость;
  • оптимизации производительности через JIT;
  • минимизация «DLL Hell» за счёт строгих имён сборок[46].

Недостатки

  • Зависимость от платформы .NET. Хотя сама платформа является кроссплатформенной, приложение требует для запуска установленной среды выполнения (за исключением сборок Native AOT).
  • «Холодный старт». При первом запуске может наблюдаться задержка из-за JIT-компиляции кода. Этот недостаток частично или полностью устраняется с помощью технологий предварительной компиляции (Crossgen2 и Native AOT).
  • Ограниченный прямой доступ к низкоуровневым возможностям оборудования и операционной системы, что является характерной чертой управляемых сред.
  • Упразднение моделей безопасности .NET Framework. Механизмы для создания внутрипроцессных «песочниц», такие как Code Access Security (CAS) и Security Transparency, были полностью удалены в .NET Core и последующих версиях. Задачи по изоляции кода теперь решаются на уровне операционной системы, например, с помощью контейнеров.

Сферы применения

  • веб-приложения (ASP.NET Core);
  • настольные приложения (Windows Forms, WPF, .NET MAUI);
  • мобильные приложения (Xamarin, .NET MAUI);
  • облачные сервисы и микросервисы;
  • игровые проекты (движок Unity);
  • IoT-устройства[47].

Инструменты и средства разработки

  • IDE: Visual Studio, Visual Studio Code + OmniSharp, JetBrains Rider, MonoDevelop;
  • Командные утилиты: .NET CLI, MSBuild, NuGet, clrver.exe, dotnet-trace/dump/counters;
  • Отладка: встроенные отладчики Visual Studio и Rider, WinDbg + SOS;
  • Профилирование: Visual Studio Profiler, PerfView, dotTrace, dotMemory;
  • Декомпиляция: ILSpy, dnSpy, dotPeek, .NET Reflector;
  • Статический анализ и тестирование: ReSharper, NDepend, NUnit, BenchmarkDotNet[48].

Аналоги и альтернативы

  • JVM — виртуальная машина Java, выполняющая байт-код Java;
  • CoreCLR — кроссплатформенная реализация CLR для .NET 5+;
  • BEAM — виртуальная машина Erlang для распределённых систем[49];
  • V8 — JIT-движок JavaScript, используемый в Chrome и Node.js[50].

Примечания