open atlas
↑ К треку
Базовый CS с нуля CS · 04 · 04

Среда выполнения (runtime)

Программа никогда не работает одна. Среда выполнения — это вспомогательный код и механизмы рядом с программой: управление памятью, сборка мусора, стек вызовов, стандартная библиотека и сам интерпретатор или виртуальная машина. Твой код стоит поверх всего этого.

CS ◷ 20 min

Ты пишешь функцию на TypeScript. Вызываешь её. Она работает. Ты создаёшь массив. Массив появляется. Ты выбрасываешь ошибку. Она распространяется вверх по цепочке вызовов. Ты никогда не думал о том, по какому адресу в памяти находится массив, что происходит с ним, когда он больше не нужен, или как ошибка знает, к какой функции вернуться.

Обо всём этом заботится нечто, что всегда работает рядом с твоей программой, незаметно. Это нечто называется средой выполнения (или runtime-системой, или runtime- средой).

Среда выполнения — это не твой код. Ты её не писал. Она написана разработчиками языка и поставляется как часть установки языка. Но она работает всё время, пока работает твоя программа, выполняя важную работу, от которой зависит твой код. Понять среду выполнения — значит понять, что реально находится под поверхностью каждой программы, которую ты когда- либо запускал, включая программы, которые пишешь сам.

Цель

После этого урока ты сможешь определить, что такое runtime-система, назвать четыре главных сервиса, которые предоставляют среды выполнения (управление памятью, механизм стека вызовов, стандартная библиотека и сам интерпретатор или виртуальная машина), объяснить, что такое сборка мусора и зачем она нужна, и описать, как твой код стоит поверх среды выполнения.

1

Что такое среда выполнения. Runtime-система (часто просто среда выполнения или runtime) — это код и механизмы, которые реализация языка устанавливает рядом с твоей скомпилированной или интерпретируемой программой для обеспечения сервисов, которые CPU и ОС сами по себе не предоставляют.

Когда ты устанавливаешь Python, Node.js или Java Development Kit, ты не просто устанавливаешь компилятор или интерпретатор — ты устанавливаешь среду выполнения. Когда твоя программа запускается, среда выполнения запускается тоже, часто до выполнения первой строки твоего кода. Среда выполнения остаётся активной на протяжении всего времени жизни процесса и выполняет важные сервисы, которые твой код делегирует ей молча.

Можно думать об этом так: CPU обеспечивает сырое выполнение (выборка, декодирование, исполнение). ОС обеспечивает процессы, файлы и доступ к железу. Среда выполнения располагается между языком и ОС, предоставляя высокоуровневые сервисы, которые обещает семантика языка: автоматическое управление памятью, безопасные соглашения о вызовах, встроенные структуры данных и так далее.

Даже компилируемые языки имеют среды выполнения. Программа на C, скомпилированная в двоичный файл, всё равно компонуется со стандартной библиотекой C (libc), которая предоставляет printf, malloc, free, memcpy и сотни других функций. Runtime C также обрабатывает запуск программы (настройка стека, вызов main) и выход из программы (сброс буферов, вызов зарегистрированных функций очистки). В C среда выполнения тоньше, чем в Python, но она всегда присутствует.

2

Сервис 1: управление памятью и сборка мусора. Каждый раз, когда твоя программа создаёт новый объект, новый массив или новую строку, ей нужен кусок памяти для его хранения. На голой машине получение памяти означает вызов OS-сервиса для получения блока адресов и самостоятельное отслеживание этих адресов. Большинство языков высокого уровня скрывают это: среда выполнения управляет областью памяти, называемой кучей (heap), и выдаёт её части, когда твой код создаёт новые значения.

В таких языках как C ты управляешь памятью вручную: вызываешь malloc для выделения блока и free для его освобождения, когда закончил. Если забудешь освободить — эта память будет занята на протяжении всей жизни процесса — утечка памяти. Если освободишь память, которую всё ещё используешь — получишь сбой или уязвимость безопасности — использование после освобождения.

Большинство современных языков (Java, Python, JavaScript, Go, C#) избегают этого, предоставляя автоматическое управление памятью через компонент под названием сборщик мусора (GC). Сборщик мусора — это часть среды выполнения, которая периодически сканирует кучу для нахождения всех объектов, которые твоя программа больше не может достичь (объектов без оставшихся ссылок из каких-либо живых переменных). Затем он автоматически освобождает эти объекты. Твой код просто создаёт объекты; GC убирает за тобой.

GC не бесплатен. Он периодически запускает свой машинный код, приостанавливая или работая параллельно с твоей программой. Это добавляет задержку и потребляет время CPU, что является одной из причин, по которой тщательно оптимизированный код на C или Rust может превосходить Python или JavaScript при CPU-интенсивных вычислениях.

3

Сервис 2: механизм стека вызовов. Каждый вызов функции должен отслеживать локальные переменные и место для возврата после завершения функции. Стек вызовов — это область памяти, где хранится эта информация. Каждый вызов функции помещает кадр стека на стек вызовов; кадр содержит локальные переменные функции, адрес возврата (куда возобновить выполнение после возврата функции) и некоторые служебные данные. Когда функция возвращает управление, её кадр снимается со стека.

Механизм стека вызовов — правила того, как кадры располагаются в памяти, как аргументы передаются между функциями, как сохраняется и восстанавливается адрес возврата — является частью ответственности среды выполнения. Для большинства компилируемых языков это определяется соглашением о вызовах платформы (стандарт, согласованный ОС и компилятором). Для интерпретируемых языков и виртуальных машин среда выполнения управляет виртуальным стеком вызовов в программном обеспечении.

Ты полагаешься на стек вызовов каждый раз, когда вызываешь функцию, даже если никогда не думаешь об этом. Когда ошибка времени выполнения сообщает «stack overflow» (переполнение стека — стек вызовов исчерпал отведённую память), это означает, что стек вызовов вырос настолько глубоко, что исчерпал память, выделенную для него средой выполнения. Чаще всего причина — бесконечная рекурсия.

4

Сервис 3: стандартная библиотека. Стандартная библиотека — это коллекция функций и структур данных, поставляемая вместе с языком и доступная без установки каких-либо дополнительных пакетов. Примеры: len(), range(), open() в Python и модуль json. Array.prototype.map(), Math.sqrt() и Date в JavaScript. java.util.ArrayList и java.io.File в Java.

Стандартная библиотека является частью среды выполнения. Когда твоя программа на Python вызывает len(my_list), она вызывает функцию, код которой находится в двоичном файле среды выполнения Python, а не в твоей программе. Среда выполнения поставляет код; твой код вызывает его.

Стандартные библиотеки обычно включают: структуры данных (списки, словари, множества), строковые операции, математические функции, файловый и сетевой ввод-вывод, работу с датами и временем, примитивы параллелизма и утилиты форматирования/разбора. Качество и полнота стандартной библиотеки языка — важный фактор продуктивности программиста. Философия Python «batteries included» (богатая стандартная библиотека) — одна из причин его популярности для скриптинга и работы с данными.

5

Сервис 4: интерпретатор или виртуальная машина. Если язык интерпретируется или компилируется в байт-код, среда выполнения также включает движок, который реально запускает твой код. Для Python это интерпретатор CPython. Для Java и Kotlin — JVM. Для C# и F# — .NET Common Language Runtime (CLR). Для JavaScript в браузере или Node.js — движки V8, SpiderMonkey или JavaScriptCore.

Эти движки сами являются скомпилированными двоичными файлами — программами, написанными на C или C++ и заранее скомпилированными для хост-платформы. Они запускают твой код, интерпретируя его или компилируя в нативный код (через JIT). Движок является частью среды выполнения в той же мере, что и сборщик мусора и стандартная библиотека.

Когда ты запускаешь node server.js, ты запускаешь двоичный файл Node.js (который включает V8, сборщик мусора, стандартную библиотеку Node.js и механизм цикла событий). Всё это — среда выполнения. Твой файл server.js — это пользовательский код поверх неё.

Почему это работает

Почему ОС не может просто предоставить все эти сервисы? ОС предоставляет среду процессов общего назначения — сырую память, файловые дескрипторы, сетевые сокеты, потоки. Она не знает и не заботится о системе типов твоего языка, твоей модели объектов или о том, как твой язык определяет область видимости и замыкания. Среда выполнения является мостом между обобщённым интерфейсом ОС и конкретной семантикой твоего языка. Среда выполнения Java знает о Java-объектах, Java-типах и Java-модели памяти. ОС знает только о страницах памяти и системных вызовах. Среда выполнения — это адаптер, который превращает сырые сервисы ОС в высокоуровневые обещания языка.

Граничные случаи

Языки почти без среды выполнения. C и Rust часто называют «системными языками», потому что они имеют очень тонкие среды выполнения. Двоичный файл Rust, скомпилированный с #![no_std], практически не имеет среды выполнения вообще — нет GC, нет стандартной библиотеки, нет механизма обработки исключений. Программист управляет памятью вручную (в Rust — через правила владения, обеспечиваемые компилятором, а не runtime GC). Это делает такие программы подходящими для микроконтроллеров и ядер ОС, где нет базовой ОС или среды выполнения, на которую можно положиться, но ответственность возвращается к программисту.

твой код
верх
стд. библ.
слой 2
GC / куча
слой 3
интерпр. / ВМ
слой 4
ОС + CPU
низ
Твой код (синий) стоит поверх слоёв среды выполнения (зелёный): стандартная библиотека, сборщик мусора и менеджер кучи, интерпретатор или виртуальная машина. Ниже всех — ОС и CPU. Среда выполнения — это всё между твоим кодом и ОС.
Разбор примера

Прослеживание сервисов среды выполнения, задействованных в одной строке TypeScript.

Рассмотрим эту строку в программе Node.js:

const users = JSON.parse(fs.readFileSync("users.json", "utf8"));

Эта одна строка одновременно задействует как минимум четыре сервиса среды выполнения:

  1. Стандартная библиотека — fs.readFileSync: Твой код вызывает функцию стандартной библиотеки Node.js readFileSync. Среда выполнения делает системный вызов ОС для открытия файла, чтения его байтов в буфер памяти и возврата буфера. Ты никогда не писал системный вызов ОС; это сделала среда выполнения.

  2. Стандартная библиотека — JSON.parse: Твой код вызывает встроенный метод JavaScript JSON.parse. Среда выполнения запускает код разбора JSON (который находится в C++-исходнике V8, а не в твоём TypeScript). Во время разбора на куче выделяются промежуточные объекты.

  3. Куча / сборщик мусора: Разобранный результат — объект с полями, возможно вложенными массивами — выделяется на куче средой выполнения. Ты никогда не вызывал malloc. Среда выполнения нашла блок памяти кучи и поместила туда объект. Когда users выйдет из области видимости, GC в конечном итоге освободит эту память.

  4. Механизм стека вызовов: readFileSync и JSON.parse — оба вызовы функций. Среда выполнения помещала и снимала кадры стека для каждого, управляя адресами возврата и локальными переменными. Среда выполнения также обрабатывает случай, когда внутри readFileSync выбрасывается исключение (файл не найден), разматывая стек вызовов для распространения ошибки к ближайшему блоку try/catch твоего кода.

Ничего из этого не присутствует в твоей одной строке TypeScript. Всё это делает среда выполнения.

Практика 0 / 5

Сборщик мусора автоматически освобождает память, которую программа больше не может достичь. В языках без GC (например, C) кто отвечает за освобождение памяти? Введи 1 (программист, вручную) или 2 (ОС, автоматически).

Стек вызовов хранит кадры стека, по одному на каждый активный вызов функции. Каждый кадр содержит локальные переменные функции и адрес возврата. Что сообщает CPU адрес возврата? Введи 1 (куда продолжить после возврата функции) или 2 (имя функции в виде строки).

Когда ты вызываешь 'JSON.parse(text)' в JavaScript, какой программный компонент содержит реальный код разбора JSON? Введи 1 (твой JavaScript-файл) или 2 (среда выполнения JavaScript — V8 или аналог).

Программа запускает «бесконечную рекурсию» — каждый вызов немедленно вызывает себя снова. Какой сервис среды выполнения даёт сбой и вызывает крах? Введи 1 (сборщик мусора) или 2 (стек вызовов).

Даже скомпилированная программа на C имеет среду выполнения. Runtime C выполняет две задачи при запуске до вызова main(). Введи 1, если 'настройка стека и инициализация процесса' является одной из этих задач, или 0, если нет.

Проверь себя
Викторина

Что такое среда выполнения и как твой код соотносится с ней?

Итог

Runtime-система (среда выполнения) — это код и механизмы, которые реализация языка предоставляет и запускает рядом с твоей программой. Она обеспечивает четыре главных сервиса: (1) управление памятью — выделение пространства кучи для новых объектов и, в большинстве современных языков, сборщик мусора, который автоматически освобождает память, на которую твой код больше не ссылается; (2) механизм стека вызовов — кадры стека, адреса возврата и соглашения о вызовах, которые обеспечивают корректную работу вызовов и возвратов функций; (3) стандартная библиотека — коллекция встроенных функций и структур данных (ввод-вывод, коллекции, математика, строки), поставляемая с языком; и (4) сам интерпретатор или виртуальная машина для интерпретируемых языков или языков с байт-кодом. Твой код стоит поверх всех этих слоёв. Даже компилируемые языки имеют среды выполнения (runtime C обрабатывает запуск, стандартную библиотеку и настройку стека). Чем богаче среда выполнения, тем больше работы она делает за тебя — ценой некоторого времени CPU и памяти для собственных нужд среды выполнения. Теперь, когда на графике задержек ты видишь всплеск паузы GC, в отчёте о сбое — stack overflow, а в описании языка — «batteries included», ты будешь знать, какой именно сервис среды выполнения задействован — и почему он существует.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.