JVM

JVM (рус. виртуальная машина Java, англ. Java Virtual Machine) — программная виртуальная машина, являющаяся ядром среды выполнения Java (JRE). Она исполняет байт-код классов Java и ряд других языков, абстрагируя программный код от конкретного оборудования и операционной системы и тем самым реализуя принцип «Write Once, Run Anywhere» (WORA)[4].

Что важно знать
JVM
англ. Java Virtual Machine
Область использования Программирование, Виртуализация
Дата появления 1995[1]
Место появления Sun Microsystems[2]
Автор понятия Джеймс Гослинг[3]

Определение

JVM представляет собой многоуровневую программную платформу, которая:

  • интерпретирует или JIT-компилирует Java-байт-код в машинные инструкции целевой платформы[5];
  • динамически загружает классы в память и проводит их верификацию на корректность и безопасность[6];
  • автоматически управляет памятью приложения с помощью встроенного сборщика мусора, предотвращая утечки памяти[7];
  • предоставляет нативные механизмы многопоточности и синхронизации[8];
  • обеспечивает обработку исключений и встроенные средства безопасности (верификация байт-кода, «песочница»)[9].

История

История создания JVM неразрывно связана с разработкой языка Java. В 1991 году в компании Sun Microsystems стартовал внутренний исследовательский проект под кодовым названием «Green Project». Его возглавил Джеймс Гослинг, а в команду разработчиков входили также Майк Шеридан и Патрик Ноутон[10]. Целью проекта было создание программной платформы для бытовой электроники, такой как интерактивное телевидение[11]. Основной проблемой была необходимость поддержки множества различных процессоров, что требовало переписывания кода для каждого устройства. Для решения этой задачи Гослинг предложил концепцию виртуальной машины — промежуточного программного слоя, который бы исполнял универсальный байт-код и транслировал его в инструкции для конкретной аппаратной платформы[12]. Первая реализация JVM была создана Гослингом в 1992 году, а язык, для которого она предназначалась, изначально носил название «Oak»[11][13].

Официальный публичный анонс языка Java и виртуальной машины Java состоялся 23 мая 1995 года на конференции SunWorld '95[10][14]. JVM стала ключевым компонентом новой платформы, реализуя принцип «Write Once, Run Anywhere» (WORA, «напиши один раз, запускай везде»). Вскоре после презентации компания Netscape объявила о намерении встроить поддержку Java в свой браузер, что способствовало быстрому росту популярности технологии[14].

В 1996 году Sun Microsystems выпустила первую официальную спецификацию JVM, известную как «Голубая книга JVM» (англ. The JVM Specification или «Blue Book»)[15]. Этот документ, авторами которого выступили Тим Линдхольм, Фрэнк Йеллин, Гилад Браха и Алекс Бакли, формализовал архитектуру и поведение виртуальной машины[16]. Публикация спецификации стала отраслевым стандартом и позволила сторонним разработчикам создавать собственные, совместимые с Java, реализации JVM[15].

В 2009—2010 годах компания Sun Microsystems была поглощена корпорацией Oracle, которая с тех пор владеет правами на торговую марку Java и продолжает развитие платформы, включая JVM[17].

Структурные элементы JVM

JVM логически делится на несколько подсистем и областей памяти[18][19]:

  • Подсистема загрузчиков классов (англ. Class Loader Subsystem) — ищет, загружает и связывает *.class-файлы.
  • Области данных времени выполнения (англ. Runtime Data Areas)
    • англ. Method Area / англ. Metaspace — метаданные классов и пул констант.
    • англ. Heap — область, в которой размещаются все объекты, создаваемые приложением.
    • Стек JVM каждого потока (англ. JVM Language Stacks) — хранит кадры вызовов методов (фреймы), локальные переменные и промежуточные результаты вычислений.
    • PC-регистр (англ. Program Counter Register) — содержит адрес текущей исполняемой инструкции для каждого потока.
    • англ. Native Method Stack — стек для данных нативных (JNI) вызовов.
  • Исполнительный механизм (англ. Execution Engine)
  • Интерфейс для вызова платформенно-ориентированного кода (англ. Java Native Interface, JNI) / англ. Java Native Access, JNA — интерфейс взаимодействия с нативными библиотеками.

В рамках развития платформы в HotSpot JVM была представлена функция компактных заголовков объектов (англ. Compact Object Headers). Она появилась как экспериментальная в JDK 24 (JEP 450)[20] и стала стабильной в JDK 25 (JEP 519)[21]. Эта функция позволяет уменьшить размер заголовка объекта на 64-битных архитектурах с 12–16 байт до 8 байт[22]. Такое изменение приводит к сокращению потребления памяти в куче, что, в свою очередь, может снизить частоту сборок мусора и повысить общую производительность приложения[23][24].

Загрузчик классов

Подсистема включает три иерархически связанных загрузчика:[25]

  1. Bootstrap ClassLoader — загружает базовые классы JDK из JAVA_HOME/lib.
  2. Platform (Class Path) ClassLoader — подключает расширенные модули JDK.
  3. Application ClassLoader — читает классы приложения из переменной CLASSPATH.

Модель делегирования предусматривает передачу запроса родительскому загрузчику до попытки загрузки в дочернем.

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

JVM работает внутри JRE, которая дополнительно содержит:

  • стандартную библиотеку классов (Java API);
  • механизмы развёртывания (Java Web Start, Plug-in);
  • вспомогательные файлы конфигурации и безопасности[26].

Сборщик мусора

Автоматическая сборка мусора освобождает память от недостижимых объектов, опираясь на поколения кучи и алгоритм «пометка – очистка – компактация». В HotSpot доступны сборщики Serial GC, Parallel GC, CMS, G1 (по умолчанию с JDK 9), а также низкопаузные ZGC и Shenandoah[27][28].

В период с 2020 по 2025 год развитие сборщиков мусора было сосредоточено на улучшении производительности и снижении пауз в низкозатратных сборщиках ZGC и Shenandoah, а также на внедрении в них поддержки поколений.

ZGC (Z Garbage Collector) и Shenandoah были переведены из экспериментального статуса в полноценный в JDK 15 (сентябрь 2020 года)[29][30]. В JDK 16 ZGC получил обновление (JEP 376), которое переместило обработку стеков потоков в конкурентную фазу, позволив достигать пауз длительностью менее одной миллисекунды[31]. Ключевым нововведением стало появление поколенческого ZGC (англ. Generational ZGC) в JDK 21 (JEP 439), что позволило снизить накладные расходы процессора и требования к объёму кучи[32]. Начиная с JDK 24, не-поколенческий режим ZGC был удалён (JEP 490), и поколенческий режим стал единственным доступным вариантом[33].

Для Shenandoah поддержка поколений была представлена как экспериментальная функция в JDK 24 (JEP 404)[34] и стала полноценной в JDK 25 (JEP 521)[35]. Сборщик G1, оставаясь стандартным, также получил улучшения: в JDK 18 были значительно сокращены его накладные расходы на нативную память[36], а в JDK 21 добавлена возможность перемещать «гигантские» объекты (англ. Humongous Objects) во время полной сборки для борьбы с фрагментацией памяти[37].

Этапы работы JVM

При запуске приложения виртуальная машина проходит серию фаз:[38]

  • загрузка класса;
  • связывание (проверка, подготовка, резольвинг);
  • инициализация;
  • исполнение байт-кода;
  • регулярные циклы сборки мусора.

Загрузка классов

Загрузка происходит лениво, при первом обращении к типу. Подсистема Class Loader формирует объект Class и сохраняет его метаданные в Metaspace[6].

Проверка, подготовка и резольвинг

На этапе проверки байт-код валидируется на предмет корректности стека и доступа. Подготовка выделяет память под статические поля и присваивает значения по умолчанию, а резольвинг заменяет символические ссылки из пула констант прямыми адресами[39].

Инициализация

JVM исполняет статические блоки и устанавливает заданные разработчиком значения статических полей. Инициализация суперкласса всегда предшествует инициализации подкласса[40].

Выполнение байткода

Execution Engine читает инструкции, интерпретирует их либо передаёт «горячие» участки в JIT-компилятор, преобразующий их в нативный код для повышения производительности[41].

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

GC-поток (или потоки) периодически помечают недостижимые объекты и освобождают их память; при необходимости выполняется компактизация для устранения фрагментации[42].

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

  • Minor GC — быстрая очистка молодого поколения (Eden + Survivor Spaces).
  • Major / Full GC — обработка старого поколения и, опционально, всей кучи[43].

Преимущества и недостатки

Ключевые характеристики JVM порождают как сильные, так и слабые стороны.

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

  • Кроссплатформенность (WORA)[5].
  • Автоматическое управление памятью (GC)[44].
  • Встроенные механизмы безопасности (верификация, sandbox)[9].
  • JIT-компиляция и адаптивная оптимизация производительности[45].
  • Богатая экосистема библиотек и поддержка множества языков (Kotlin, Scala, Groovy)[8].

Недостатки

  • Сравнительно высокие затраты по памяти и CPU, особенно при старте[46].
  • Паузы GC при неудачной конфигурации[47].
  • Сложность тонкой настройки многочисленных параметров запуска[48].
  • Ограничения при работе с C-расширениями и нативной памятью[49].

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

  • Серверная веб-разработка (Tomcat, Jetty, Spring).
  • Корпоративные информационные системы (Java EE / Jakarta EE).
  • Android (Dalvik / ART основаны на концепции JVM).
  • Научно-вычислительные и финансовые приложения.
  • Встроенные и IoT-устройства.
  • Контейнеризированные облачные микросервисы[44].

Инструменты для работы с JVM

  • JDK — полный комплект для разработки, включающий компилятор javac, отладчик jdb и т.д.
  • JRE — минимальный набор для запуска приложений (JVM + стандартные библиотеки)[50].

Реализации JVM

  • HotSpot (Oracle / OpenJDK) — эталонная реализация с адаптивной JIT-оптимизацией[51].
  • Eclipse OpenJ9 — лёгкая и быстрая JVM от IBM/Eclipse, оптимизированная под контейнеры[52].
  • GraalVM — поддерживает многозначные языки и AOT-компиляцию в нативные образы[53].
  • Dalvik / ART — среда выполнения Android, использующая формат DEX и смешанную AOT/JIT-схему[54].

Средства мониторинга и профилирования JVM

  • JConsole — встроенный JMX-клиент для мониторинга памяти, потоков и MBeans[55].
  • VisualVM — графический анализатор CPU, памяти, heap / thread dump[56].
  • Java Mission Control (JMC) и Java Flight Recorder (JFR) — инструменты для низкозатратного сбора и анализа телеметрии приложения[57]. Начиная с JDK 25, JFR получил значительные улучшения:
    • JFR Method Timing & Tracing (JEP 520) — добавлены новые события (jdk.MethodTiming и jdk.MethodTrace) для профилирования времени выполнения и трассировки вызовов конкретных методов без изменения кода приложения[58].
    • JFR CPU-Time Profiling (JEP 509) — представлен экспериментальный механизм для более точного профилирования использования процессорного времени на уровне ядра Linux[58].
    • JFR Cooperative Sampling (JEP 518) — улучшена стабильность сбора сэмплов стеков вызовов, что повышает надёжность профилирования[59].
  • JProfiler и YourKit — коммерческие профилировщики с детальным CPU/Memory анализом[60].

Инструменты отладки JVM

  • jdb — консольный отладчик JDK.
  • JDWP — протокол удалённой отладки.
  • JDI — высокоуровневый Java-API отладчика.
  • JVMTI — нативный интерфейс инструментов для тонкой интроспекции и управления исполняющей VM[61].

Интеграция JVM с другими платформами

  • .NET — IKVM.NET (запуск Java-кода в CLR).
  • Python — Jython (Python 2 на JVM) и Py4J (RPC-мост).
  • Node.js — J2V8 / node-java (биндинги V8 через JNI).
  • R — пакет rJava и проект Renjin.
  • C/C++ — JNI и Invocation API, позволяющие встраивать JVM в нативные приложения[62].

Современное развитие и новые возможности

Развитие JVM в 2024—2025 годах, в рамках релизов JDK 24 и JDK 25 (LTS), было сосредоточено на повышении производительности, сокращении потребления памяти и внедрении новых API для упрощения разработки высоконагруженных приложений.

Ключевые API и языковые функции:

  • Scoped Values (англ. Scoped Values) — стали финальной функцией в JDK 25 (JEP 506). Они предоставляют механизм для обмена неизменяемыми данными внутри одного потока и между дочерними потоками, особенно при использовании виртуальных потоков (Project Loom). Scoped Values являются более производительной и надёжной альтернативой механизму ThreadLocal.
  • Vector API — продолжил развитие (десятая инкубация в JDK 25, JEP 508). Этот API позволяет выполнять векторные вычисления, которые во время выполнения компилируются в оптимальные SIMD-инструкции (англ. Single Instruction, Multiple Data) на поддерживаемых архитектурах процессоров, что обеспечивает производительность, значительно превосходящую скалярные вычисления.
  • Структурная многопоточность (англ. Structured Concurrency) — остаётся в статусе предварительного просмотра (англ. Preview) в JDK 25 (JEP 505). API упрощает многопоточное программирование, позволяя рассматривать группу задач, выполняющихся в разных потоках, как единое целое, что облегчает обработку ошибок и отмену операций.

На уровне самой JVM были внедрены значительные оптимизации. Функция компактных заголовков объектов (англ. Compact Object Headers), представленная как экспериментальная в JDK 24 (JEP 450) и ставшая стабильной в JDK 25 (JEP 519), позволяет уменьшить размер заголовка объекта на 64-битных архитектурах. Это приводит к сокращению потребления памяти в куче и, как следствие, к снижению частоты сборок мусора. Для ускорения запуска приложений в JDK 25 были улучшены механизмы AOT-компиляции (JEP 514), что упростило создание и использование AOT-кэшей через командную строку.

Важным шагом в эволюции платформы стал полный отказ от поддержки 32-битных операционных систем на архитектуре x86 в JDK 25 (JEP 503). Ранее, в JDK 24, был окончательно удалён устаревший механизм Security Manager (JEP 486)[63].

Примечания