Объяснение архитектуры JVM

Каждый разработчик Java знает, что байткод выполняется при помощи JRE (Java Runtime Environment). Но многие не знают того факта, что JRE это имплементация виртуальной машины Java (JVM), которая анализирует, интерпретирует и исполняет байткод. Для разработчика очень важно знать архитектуру виртуальной машины Java, т.к. это позволяет нам писать код более эффективно. В данной статье мы изучим глубины архитектуры JVM и различные компоненты виртуальной машины.

Что такое JVM?

Виртуальная машина — это программная имплементация реальной машины. При разработке Java использовалась концепция WORA (Write Once Run Anywere) – пиши однажды, запускай везде, которая используется в виртуальной машине. Компилятор компилирует файлы Java в файлы .class, затем данные файлы являются входом для виртуальной машины JVM, которая загружает и исполняет .class файл. Ниже вы можете видеть архитектуру виртуальной машины JVM.

Диаграмма архитектуры JVM

Как виртуальная машина работает?

Как показано на диаграмме выше, JVM разделена на три основные подсистемы:

  1. Подсистема загрузчика класса (Class Loader Subsystem)
  2. Область данных времени выполнения (Runtime Data Area)
  3. Механизм исполнения (Execution Engine)

Подсистема загрузчика класса

Функциональность динамической загрузки классов Java предоставляется подсистемой загрузчика классов. Она загружает, связывает и инициализирует файл класса, первый раз, когда появляется первая ссылка на класс во времени исполнения, но не времени компиляции.

Загрузка

Boot Strap class Loader, Extension class Loader и Application class Loader – три загрузчика, которые выполняют загрузку классов.

  1. Boot Strap ClassLoader – загружает классы начальной загрузки только лишь из библиотеки rt.jar. Данному загрузчику предоставляется наивысший приоритет.
  2. Extension ClassLoader – загружает классы расположенные внутри папки ext (jre\lib)
  3. Application ClassLoader – загружает классы специфичные для приложения из путей, которые указаны в переменных среды и т.п.

Данные загрузчики классов будут следовать иерархическому алгоритму делегирования (Delegation Hierarchy Algorithm) во время загрузки классов.

Связывание

  1. Verify – верификатор байткода проверяет байткод на корректность и если верификация не проходит, то мы увидим ошибку верификации.
  2. Prepare – для всех статических переменных выделяется память и инициализируются значениями по умолчанию.
  3. Resolve – все символические ссылки заменяются реальными ссылками из области метода (Method Area)

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

Это завершающая фаза загрузки классов, в ней всем статическим переменным будут присвоены фактические значения, также во время этой фазы выполняются блоки статической инициализации.

Область данных времени выполнения (Runtime Data Area)

Область данных времени выполнения состоит из пяти основных компонентов:

  1. Method area – все данные уровня класса, включая статические переменные сохраняются здесь. Область метода единственная для JVM и является разделяемым ресурсом.
  2. Heap area – все объекты и соответствующие им переменные экземпляров объектов, а также массивы сохраняются здесь. Область кучи также является единственной для виртуальной машины и разделяет данные между множественными потоками. Данные расположенные здесь не являются потокобезопасными.
  3. Stack area – для каждого потока создается выделенный стек времени выполнения. Для каждого вызова метода создаётся элемент в стеке, который называется фрейм стека (Stack Frame). Все локальные переменные создаются в памяти стека. Область стека потокобезопасна, т.к. не является разделяемым ресурсом. Фрейм стека состоит из трех частей:
    1. Массив локальных переменных (Local Variable Array) – применительно к методу определяет какие локальные переменные участвовали при вызове метода и соответствующие значения этих переменных.
    2. Стек операндов (Operand Stack) – если существуют промежуточные операции требующие выполнения, то стек операндов выступает в качестве рабочего пространства времени выполнения для исполнения операции.
    3. Данные фрейма – все символы соответствующего метода сохраняются здесь. В случае любой исключительной ситуации, блок catch будет управлять этими данными.
  4. PC Registers – каждый поток имеет выделенные регистры PC чтобы держать информацию о адресе текущей выполняемой инструкции. Как только инструкция будет выполнена, регистры будут обновлены следующей инструкцией.
  5. Native Method stacks – стек нативных методов содержит информацию о нативных методах. Для каждого потока создается выделенный стек нативных методов.

Механизм исполнения (Execution Engine)

Байткод, находящийся в области данных времени выполнения будет выполнен через механизм исполнения. Механизм исполнения читает байткод и выполняет его друг за другом.

  1. Интерпретатор – читает байткод, интерпретирует его и выполняет шаг за шагом. Интерпретатор выполняет интерпретацию кода быстрее его выполнения. Недостаток интерпретатора заключается в том, что, когда один метод вызывается множество раз, каждый раз требуется его интерпретация.
  2. JIT компилятор – JIT компилятор устраняет недостаток присущий интерпретатору (вызов одного метода несколько раз приводит к необходимости интерпретации каждый раз). Механизм исполнения в первую очередь использует помощь интерпретатора, но если обнаруживается повторяемый код, то использует JIT компилятор который компилирует байткод и преобразует его в нативный код. Этот нативный код используется непосредственно для повторяющихся вызовов методов, который улучшает производительность системы.
    1. Intermediate Code generator – производит промежуточный код
    2. Code Optimizer – оптимизатор кода ответственен за оптимизацию промежуточного кода
    3. Target Code Generator – генератор целевого кода отвечает за создание машинного/нативного кода
    4. Profiler – профилировщик — это особый компонент, который отвечает за поиск мест, требующих оптимизации. Используется для определения вызывается ли метод множество раз или нет.
  3. Сборщик мусора – сборщик мусора является частью механизма исполнения, он собирает/удаляет объекты на которые отсутствуют ссылки. Вызов сборщика мусора может быть инициирован вызовом метода System.gc(), однако его запуск не гарантируется. Сборщик мусора виртуальной машины собирает только те объекты, которые созданы используя ключевое слово new (прим. переводчика: объекты созданные через механизм рефлексии собираются сборщиком мусора также).

Java Native Interface (JNI): JNI взаимодействует с библиотеками нативных методов и предоставляет нативные библиотеки требуемые механизмом исполнения.

Native Method Libraries: это набор нативных библиотек, которые требуются для механизма исполнения.

 

Статья впервые опубликована на http://www.javainterviewpoint.com/java-virtual-machine-architecture-in-java/ by Jackson Joseraj
Перевод выполнил A.Saushkin специально для Цифровой Лаборатории