JDK, JRE и JVM: как на самом деле устроена Java-платформа

Введение

Каждому начинающему Java-разработчику рано или поздно приходится разбираться с аббревиатурами JDK, JRE и JVM. Эти технологии лежат в основе платформы Java, но отвечают за разные задачи. В этой статье разберем, что они означают, чем отличаются и как связаны между собой.
Коротко их роли можно описать так:
  • JDK — комплект разработки Java. Он включает среду выполнения JRE и инструменты разработчика: компилятор javac, отладчик jdb, генератор документации javadoc и другие утилиты
  • JRE — среда выполнения Java. Она объединяет JVM, стандартную библиотеку Java и служебные компоненты, необходимые для запуска уже скомпилированных Java-программ
  • JVM — виртуальная машина Java. Она загружает классы, проверяет и выполняет содержащийся в них байт-код, управляет памятью и выполняет прочие операции
Рис. 1. Связь JDK, JRE и JVM
На рис. 1 показана логическая взаимосвязь компонентов Java-платформы: JDK включает инструменты разработки и среду выполнения, JRE содержит всё необходимое для запуска Java-программ, а JVM выполняет байт-код.
Примечание. Начиная с Java 9 платформа стала модульной: компоненты среды выполнения, которые раньше находились в каталоге jre, теперь входят в состав самой JDK как единый набор модулей. Поэтому каталог jre исчез, а скачивать JRE отдельно больше не нужно.

  1. Что такое JDK

JDK (Java Development Kit) — это комплект разработки Java, который устанавливают для создания, компиляции и запуска Java-программ.
JDK поставляется в виде готовых сборок разных поставщиков: Oracle JDK, Eclipse Temurin, Amazon Corretto, Azul Zulu, Microsoft Build of OpenJDK и других.
Условно состав JDK можно разделить на две основные части: инструменты разработчика и среду выполнения.
a
Инструменты разработчика
  • javac — компилятор Java. Он преобразует исходные файлы .java в файлы .class, содержащие байт-код
  • jdb — отладчик
  • javadoc — генератор документации
  • jar — утилита для работы с JAR-архивами
  • jshell — интерактивная консоль для экспериментов с кодом
b
Среда выполнения
  • java — команда для запуска скомпилированных Java-приложений
  • Стандартные библиотеки Java — готовые классы и API для работы с коллекциями, строками, вводом-выводом, сетью, датой и временем и многим другим
  • Служебные компоненты, необходимые для запуска и работы Java-программ
  • JVM — виртуальная машина, выполняющая байт-код

1.1 Версии Java и редакции платформ

При установке JDK требуется выбрать версию Java. Она определяет, какие возможности языка и библиотек будут доступны при компиляции. Если в коде используется возможность, появившаяся только в новой версии Java, такой код получится скомпилировать только с помощью JDK этой версии или новее. Более старый компилятор javac не сможет выполнить компиляцию и сообщит об ошибке.
Для учебных целей можно смело устанавливать последнюю версию JDK. В реальных же проектах часто выбирают LTS-версии, поскольку они рассчитаны на длительную поддержку и считаются более стабильным выбором для промышленной разработки.
Кроме версии, полезно знать о существовании разных редакций платформы Java:
  • Java SE (Java Standard Edition) — стандартная платформа Java. Именно с нее начинают большинство разработчиков
  • Java EE (Java Enterprise Edition) — прежнее название набора технологий для корпоративной разработки. Сейчас эти технологии развиваются в проекте Jakarta EE
  • Java ME (Java Micro Edition) — редакция для устройств с ограниченными ресурсами
Для большинства задач, с которых начинают разработчики, достаточно установить JDK для Java SE.

1.2 Установка JDK и первые шаги

Установка JDK обычно сводится к двум шагам: непосредственная установка JDK и настройка системных переменных JAVA_HOME и PATH. Подробности этого процесса описаны в статье «Установка и настройка Java».
После установки JDK и настройки переменной PATH команды java, javac и другие становятся доступны в командной строке. С их помощью можно компилировать исходный код и запускать Java-программы. Подробнее про эти команды читайте в статье «Компиляция и запуск Java-программ».
Традиционно первая программа на Java выводит на экран простую фразу. Как написать и запустить такую программу, описано в статье «Java с нуля: первая программа».
Теперь, когда мы разобрали JDK, перейдем к среде выполнения — JRE. Она отвечает за то, чтобы уже скомпилированная Java-программа могла запуститься и работать.

2. Что такое JRE

JRE (Java Runtime Environment) — это среда выполнения Java, то есть набор компонентов, необходимых для запуска уже скомпилированных Java-программ.
Раньше JRE часто устанавливали отдельно, если требовалось только запускать программы, а не разрабатывать их. В современных версиях Java компоненты среды выполнения входят в состав JDK, поэтому разработчику обычно достаточно установить JDK. Термин JRE при этом никуда не делся: он по-прежнему обозначает среду запуска Java-программ, в которую входят JVM, стандартные библиотеки и служебные компоненты.
JRE включает:
  • java — команду для запуска скомпилированных Java-приложений
  • Стандартные библиотеки Java — готовые классы и API, которые использует приложение
  • Служебные компоненты среды выполнения — файлы и механизмы, необходимые для запуска и работы Java-программы
  • JVM — виртуальную машину Java, которая выполняет байт-код программы
JRE выступает как промежуточный слой между Java-программой и операционной системой (ОС). ОС управляет памятью, файлами, сетью и другими ресурсами компьютера. Среда выполнения Java работает поверх нее и предоставляет программе доступ к этим ресурсам через JVM и стандартные библиотеки.
Такой подход сглаживает различия между операционными системами: один и тот же байт-код может выполняться на разных платформах, если для них есть подходящая среда выполнения Java.

2.1 Как JRE связана с JVM

JRE предоставляет окружение для запуска Java-программы, а JVM внутри этой среды выполняет байт-код.
Когда пользователь запускает Java-приложение командой java, эта команда запускает JVM. Затем JVM загружает необходимые классы, проверяет байт-код и начинает выполнение программы. Она также во время работы программы управляет памятью, выполняет сборку мусора и поддерживает работу потоков.
Рис. 2. Связь JRE с ОС

3. Что такое JVM

JVM (Java Virtual Machine) — это виртуальная машина Java. Она выполняет байт-код — промежуточный формат, в который компилятор javac преобразует исходный код .java.
Термин «виртуальная машина» означает, что JVM выступает как программный слой между Java-программой и реальным компьютером. Разработчик компилирует исходный код в байт-код, а JVM выполняет этот байт-код по единым правилам на разных платформах. Благодаря этому Java-программа не привязана напрямую к конкретной ОС и может запускаться без изменения исходного кода, если для этой системы есть подходящая JVM.
Рис. 3. Кроссплатформенность Java
Когда Java появилась в 1995 году, такой подход был значительным шагом вперед. Многие программы тогда приходилось собирать отдельно под конкретную ОС и аппаратную платформу, а в языках вроде C и C++ разработчик часто сам отвечал за управление памятью. JVM помогла решить обе проблемы: повысила переносимость программ и взяла на себя значительную часть работы с памятью.

3.1 Для чего используется JVM

У JVM есть несколько задач:
  • Выполнение байт-кода — JVM загружает скомпилированные классы, проверяет байт-код и выполняет программу
  • Переносимость Java-программ — один и тот же байт-код может выполняться на разных ОС
  • Управление памятью — JVM выделяет память для объектов и освобождает ее, когда объекты больше не используются. Автоматическое освобождение памяти называется сборкой мусора
  • Оптимизация выполнения — современные JVM не только интерпретируют байт-код, но и используют JIT-компиляцию. Это когда часто выполняемые участки кода преобразуются в машинный код во время работы программы для улучшения производительности

3.2 Три значения термина JVM

Термин JVM используют в трех связанных, но разных смыслах:
  • Спецификация JVM — официальный документ, который описывает, как должна работать виртуальная машина Java
  • Реализация JVM — конкретная программа, созданная по этой спецификации, например, HotSpot JVM или Eclipse OpenJ9
  • Экземпляр JVM — запущенный процесс виртуальной машины, который выполняет конкретное Java-приложение
3.2.1 Спецификация JVM
Спецификация JVM описывает требования к виртуальной машине Java. Она определяет формат .class-файлов, набор инструкций байт-кода, правила загрузки и проверки классов, работу с памятью, стеком, исключениями, потоками и другими механизмами выполнения программы.
Спецификация не диктует, как именно должна быть устроена JVM внутри. Разработчики конкретной реализации могут выбирать внутренние алгоритмы, способы оптимизации, устройство сборщика мусора и другие детали. Главное — чтобы реализация корректно выполняла байт-код и соответствовала требованиям спецификации.
3.2.2 Реализация JVM
Спецификация JVM сама по себе не запускает программы. Для выполнения Java-приложений требуется конкретная реализация виртуальной машины.
Самая известная реализация — HotSpot JVM. Она используется в OpenJDK и большинстве популярных сборок JDK на его основе: Oracle JDK, Eclipse Temurin, Amazon Corretto, Azul Zulu, Microsoft Build of OpenJDK и других.
HotSpot — не единственная реализация JVM. Существуют и другие, например Eclipse OpenJ9. Они тоже должны соответствовать спецификации JVM, но могут отличаться внутренним устройством, производительностью, настройками, алгоритмами сборки мусора и диагностическими возможностями.
На практике разработчики обычно не устанавливают JVM отдельно, а скачивают готовую сборку JDK, в которую уже входит JVM.
3.2.3 Экземпляр JVM
Когда вы запускаете приложение командой java, создается процесс, внутри которого работает JVM. Этот работающий процесс и называют экземпляром JVM.
Обычно, когда разработчики говорят «JVM», они имеют в виду именно работающий экземпляр JVM: процесс, который выполняет Java-приложение, использует память, загружает классы, управляет потоками и выполняет сборку мусора.
Если программа завершилась из-за ошибки, например из-за переполнения стека, говорят, что ошибка произошла в работающей JVM. Формально «ломается» не JVM как технология, а завершается конкретный процесс, в котором выполнялась программа.

3.3 Как JVM запускает Java-программу

Перед тем как выполнить Java-программу, JVM должна найти, загрузить и подготовить необходимые классы. Этот процесс включает несколько этапов: загрузку классов (поиск и чтение .class-файлов), связывание (проверку байт-кода, подготовку статических полей и разрешение ссылок) и инициализацию классов (выполнение статических инициализаторов). После этого JVM начинает выполнение основного кода приложения.
Более подробно о каждом этапе читайте в статье «Глубокое погружение в процесс запуска JVM».
3.3.1 Загрузка классов
Загрузчик классов — это часть механизма JVM, отвечающая за загрузку классов во время выполнения программы. Он находит нужные .class-файлы, загружает их в память и делает доступными для дальнейшего выполнения.
Классы обычно загружаются не все сразу, а по мере необходимости. Такой подход называют ленивой загрузкой. Если какая-то часть программы ни разу не была использована, связанные с ней классы могут так и не загрузиться во время работы приложения.
JVM кэширует уже загруженные классы и не загружает их заново при повторных обращениях.
3.3.2 Проверка байт-кода
После загрузки JVM проверяет байт-код. Эта проверка помогает убедиться, что он соответствует правилам и не нарушает базовые требования безопасности.
Также JVM проверяет корректность инструкций, работу со стеком, типы данных и другие условия. Благодаря этому ошибочный или поврежденный байт-код не должен бесконтрольно выполняться внутри виртуальной машины.
3.3.3 Выполнение байт-кода
После загрузки и проверки классов JVM начинает выполнять байт-код программы. Выполнение обычно начинается с точки входа, например с метода main, а затем продолжается по мере вызова других методов и загрузки дополнительных классов.
За выполнение байт-кода отвечает механизм выполнения JVM. Он интерпретирует инструкции байт-кода, а часто выполняемые участки кода может компилировать в машинный код с помощью JIT-компилятора. Так современные JVM не только запускают Java-программы, но и оптимизируют их работу во время выполнения.

3.4 Память в JVM

Памятью Java-программы управляет JVM. Она выделяет память для объектов, хранит данные во время выполнения программы и освобождает память от объектов, которые больше не используются.
Упрощенно память JVM можно разделить на несколько областей:
  • Heap (куча) — область памяти, в которой хранятся объекты, созданные во время работы программы. Куча является общей для всех потоков. Объект, созданный с помощью оператора new, размещается именно здесь
  • Stack (стек) — область памяти, связанная с выполнением методов. У каждого потока свой стек. В нём для каждого вызванного метода создаётся кадр (фрейм), в котором хранятся локальные переменные примитивных типов и ссылки на объекты в куче. Если цепочка вызовов методов становится слишком глубокой (например, при бесконечной рекурсии), стек переполняется и выбрасывается ошибка StackOverflowError
  • Metaspace (метапространство) — область памяти, в которой JVM хранит метаданные загруженных классов: информацию о классах, методах, полях и другие служебные данные. Metaspace размещается в native-памяти, а не в куче
JVM освобождает память в куче автоматически: объекты, на которые больше нет ссылок, удаляются сборщиком мусора. Разработчику не приходится вручную удалять объекты, как в некоторых других языках.
Размеры кучи и стека можно настраивать при запуске JVM с помощью флагов, например -Xmx (максимальный размер кучи) и -Xss (размер стека для потока). Metaspace тоже имеет настройки, но по умолчанию он растёт динамически.
Память JVM устроена сложнее: существуют и другие области, например Code Cache (для JIT-скомпилированного кода) и различные буферы в native-памяти. Но для первого знакомства достаточно понимать различие между кучей, стеком и метапространством.

3.5 Сборка мусора

В языках вроде C и C++ разработчик часто сам отвечает за выделение и освобождение памяти. В Java большую часть этой работы берет на себя JVM.
Когда в программе создается объект с помощью new, память для него выделяется в куче JVM. Разработчику не приходится вручную запрашивать память у ОС и затем освобождать ее. JVM определяет недоступные объекты и удаляет их — этот процесс называется сборкой мусора.
JVM находит мусор через механизм достижимости: объект считается живым, пока до него можно добраться по цепочке ссылок от корневых объектов — например, от локальных переменных в стеке или от статических полей классов. Объекты, до которых нельзя добраться, удаляются. Если программа создаёт слишком много объектов и куча переполняется, возникает ошибка OutOfMemoryError.
Для эффективной работы куча обычно делится на несколько поколений. Большинство объектов живут недолго и удаляются при первой же сборке мусора в young generation. Объекты, пережившие несколько сборок, перемещаются в old generation и проверяются реже.
Во время сборки мусора JVM обычно приостанавливает выполнение программы. Такие паузы называют stop-the-world. Современные сборщики, такие как G1 и ZGC, стараются минимизировать эти паузы, выполняя часть работы параллельно с приложением.
В первые годы Java часто критиковали за производительность. Java-программы выполнялись не напрямую на процессоре, как нативный код C или C++, а через JVM. Кроме того, сборка мусора могла создавать дополнительные паузы во время работы приложения. Со временем JVM стала значительно эффективнее. В современных реализациях используются разные сборщики мусора, JIT-компиляция и другие оптимизации, которые позволяют Java-приложениям показывать высокую производительность во многих сценариях.

Заключение

В этой статье мы рассмотрели ключевые технологии Java-платформы: JDK, JRE и JVM, а также разобрали, как они связаны между собой. Понимание этих понятий поможет начинающему разработчику увереннее работать с Java: правильно выбрать и установить JDK, настроить среду разработки, запускать программы и лучше понимать ошибки, возникающие при компиляции или выполнении кода. На старте этого достаточно, чтобы не путаться в базовых терминах и понимать, какую роль каждая часть Java-платформы играет в разработке и запуске приложений.
Автор: Чимаев Максим
Оцените статью, если она вам понравилась!