JVM(Java Virtual Machine) это среда для запуска Java приложений. При запуске Java программы вызывается метод main, который реализован в java коде. JVM это часть JRE (Java Runtime Engine).

JRE это среда для запуска Java приложений, не путать с JDK. JDK (Java Development Kit  - это набор инструментов, позволяющий разрабатывать приложения на Java, включающий в себя как JRE так компилятор Java кода и библиотеки для разработки Java приложений).

Java приложения называются WORA (Write Once Run Everywhere) приложениями, то есть буквально напиши один раз и запускай везде. Это означает что разработчик может создать Java код на одной операционной системе и запустить его в другой системе, в которой установлена JRE без каких-либо дополнительных настроек. Это все возможно потому что это JVM.

Когда мы компилируем .java файл, создается .class файл (java byte code) с таким же названием, этот файл генерируется Java компилятором.

Class Loader Subsystem

Class Loader Subsystem

В основном отвечает за эти три активности:

  • Загрузка
  • Ссылки
  • Инициализация

Загрузка

Загрузчик классов (Class loader) считывает .class файл, генерирует соответсвующие бинарные данные и сохраняет это в области методов.

Для каждого .class файла, JVM хранит следующую информацию в области методов:

  • Полное имя загружаемого класса и его непосредственные родительские классы.
  • Определяет является ли .class файл Java классом, интерфейсом или Enum.
  • Определяет поля, переменные и информацию о методах и т.д.

После загрузки .class файла, JVM создает объекты класса в области Heap памяти. Необходимо помнить что этот объект является типом Class, который определен в пакете java.lang. Объект этого класса программист может использовать для получения информация уровня класса, например, имя класса, имя родительского класса, методы класса, переменные и так далее. Чтобы получить все эти данные необходимо вызывать метод getClass() у этого объекта.

// A Java program to demonstrate working of a Class type
// object created by JVM to represent .class file in
// memory.

import java.lang.reflect.Field;
import java.lang.reflect.Method;

// Java code to demonstrate use of Class object
// created by JVM
public class Test {
    public static void main(String[] args) {
        Student s1 = new Student();

        // Getting hold of Class object created
        // by JVM.
        Class c1 = s1.getClass();

        // Printing type of object using c1.
        System.out.println(c1.getName());

        // getting all methods in an array
        Method m[] = c1.getDeclaredMethods();
        for (Method method : m)
            System.out.println(method.getName());

        // getting all fields in an array
        Field f[] = c1.getDeclaredFields();
        for (Field field : f)
            System.out.println(field.getName());
    }
}
// A sample class whose information is fetched above using
// its Class object.
class Student
{
    private String name;
    private int roll_No;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getRoll_no() { return roll_No; }
    public void setRoll_no(int roll_no) {
        this.roll_No = roll_no;
    }
}

Результат выполнения данной программы:

Student
getName
setName
getRoll_no
setRoll_no
name
roll_No

Заметка: Для каждого загруженного класса (.class), создается только один объект.


Ссылки

Обеспечивает проверку, подготовку и (опционально) разрешение.

  • Проверка : Гарантирует корректность .class файлов. Проверяет что конкретный  .class был скомпилирован и сгенерирован валидным компилятором. Если проверка не пройдена, то возникает run-time exception java.lang.VerifyError.  Подготовка : JVM выделяет память для переменных класса и инициализирует эти переменные в памяти значениями по умолчанию.

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

Три основные загрузчика классов (cloass loaders):

  • Bootstrap class loader:  Каждая реализация JVM должна иметь bootstrap class loader, способная загружать проверенные классы. Загружается основное API Java классов, которое находится в директории JAVA_HOME/jre/lib. Этот путь так же называют bootstrap path. Загрузчик реализован на языках C, C++.
  • Extension class loader: Это дочерний загрзчик bootstrap class loader. Он загружает классы, которые представлены в дирекотрии JAVA_HOME/jre/lib/ext(Extension path) или в любой другой директории, которая описана в системной переменной java.ext.dirs. Это функция реализована с помощью Java sun.misc.Launcher$ExtClassLoader class
  • System/Application class loader: В свою очередь это дочерний загрузчик extension class loader. Загружает классы из области приложения (application classpath). Так же реализован с помощью Java sun.misc.Launcher$ExtClassLoader class.
// Java code to demonstrate Class Loader subsystem
public class Test
{
    public static void main(String[] args)
    {
        // String class is loaded by bootstrap loader, and
        // bootstrap loader is not Java object, hence null
        System.out.println(String.class.getClassLoader());
 
        // Test class is loaded by Application loader
        System.out.println(Test.class.getClassLoader());
    }
}   

Результат выполнения программы:

bash null sun.misc.Launcher$AppClassLoader@73d16e93


Заметка : JVM следует принципа иерархического делегирования при загрузке классов. Система загрузчика классов делегирует запрос в extension class loader, а extension class loader делегирует запрос в boot-strap classloader. Если класс найден в boot-strap пути, то класс загружается, в противном случае запрос отправляется обратно в extension class loader, а затем в system class loader. И если system class loader не смог загрузить класс, то возникает run-time exception java.lang.ClassNotFoundException.

Как работает jvm

Память JVM

  • Область методов: В области методов, находится все информация о классе, например, имя класса, имена родительских классов, методы и переменные, а так же прочая информация, включая статические переменные. Существует всего одна область методов во всей JVM и это область общий ресурс для всех программ, работающих в этой JVM.
  • Heap area: Здесь хранится вся информация об объектах. Существует всего одна область Heap в JVM и доступная всем программам, которые работают в этой JVM.
  • Stack area: Для каждого потока, JVM создает свою область (run-time stack) котоаря хранится здесь. Каждый блок этого стэка называется активная запись / окно стэка (activation record/stack frame), В этой области хранятся вызовы методов. Все локальные переменные этих методов хранятся в этом выделенном стэке. После того как поток завершает свою работу, выделенный стэк уничтожается. Это не общедоступный ресурс.
  • PC Registers: Хранит адреса текущих инструкций потока. У каждого потока своя область PC Registers.
  • Native method stacks: Для каждого потока создается отдельная область Native method stacks. В этой области хранится информация о нативных методах.

Native method stacks

Движок выполнения (Execution Engine)

Execution engine выполняет байткод .class файла. Он считывает байткод последовательно, строчка за строчкой, использует информацию, которая находится в разных областях памяти JVM и выполняет инструкции кода. Можно разделить на три составляющих:

  • Интерпретатор (Interpreter): Интерпретирует байт код в команды и выполняет их. Недостаток в том что каждый раз когда нужно выполнить команду, необходимо интерпретировать байт код в понятную для JVM команду, даже если вызывается один и тот же метод несколько раз.
  • Just-In-Time Compiler(JIT): Используется для увеличения эффективности интерпретатора. Он компилирует весь байткод и преобразует его в нативный код, всякий раз когда встречает повторяющиеся вызовы методов. JIT поставляет нативный код и повторная интерпретация не нужна, поэтому увеличивается эффективность.
  • Сборщик мусора (Garbage Collector):  Уничтожает не используемые объекты.

Java Native Interface (JNI)

Это интерфейс для взаимодействия с нативными методами системных библиотек, которые могут быть написаны на C, C++. Для вызова методов из системных библиотек (например .dll или .so) необходимо указать где именно это библиотека находится, чтобы JVM знала об этой библиотеке.

Native Method Libraries

Это набор нативных библиотек (C, C++), которые необходимы для работы Execution Engine.

Данный материал является переработанным переводом этой статьи с небольшими дополнениями.