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

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

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

Когда мы компилируем файл .java, компилятор Java генерирует файлы .class (содержащие байт-код) с такими же именами классов, которые присутствуют в файле .java. Этот файл .class проходит различные этапы, когда мы его запускаем. Эти шаги вместе описывают всю JVM.

Подсистема загрузчика классов (Class Loader Subsystem)

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

  • загрузка (Loading)
  • связывание (Linking)
  • инициализация (Initialization)

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

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

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

// Java программа для демонстрации работы объекта типа Class,
// созданного JVM для представления файла .class в
// памяти.
import java.lang.reflect.Field; 
import java.lang.reflect.Method; 
  
// Java код для демонстрации использования объекта Class
// созданного JVM
public class Main { 
    public static void main(String[] args) { 
        Programmer programmer = new Programmer(); 
  
        // Получение объекта Class созданого JVM
        Class c1 = programmer.getClass(); 
  
        // Печать типа объекта с использованием c1. 
        System.out.println(c1.getName()); 
  
        // получение всех методов в массив 
        Method m[] = c1.getDeclaredMethods(); 
        for (Method method : m) 
            System.out.println(method.getName()); 
  
        // получение всех полей в массив 
        Field f[] = c1.getDeclaredFields(); 
        for (Field field : f) 
            System.out.println(field.getName()); 
    } 
} 
  
// Пример класса, информация которого извлекается выше 
// с использованием его Class объекта.
class Programmer { 
    private String name; 
    private int age; 
  
    public String getName()  {  return name;   } 
    public void setName(String name) { this.name = name; } 
    public int getAge()  { return age;  } 
    public void setAge(int age) { 
        this.age = age; 
    } 
} 

Вывод:

Programmer
getName
setName
setAge
getAge
name
age

Примечание. Для каждого загруженного файла .class создается только один объект класса.

Programmer p2 = new Programmer();
// c2 будет указывать на тот же самый объект, 
// на который указывает и c1
Class c2 = s2.getClass();
System.out.println(c1==c2); // true

Связывание (Linking): выполняет проверку, подготовку и (необязательно) разрешение.

  • Проверка (Verification): обеспечивает правильность файла .class, то есть проверяет, правильно ли отформатирован этот файл и создан ли он корректным компилятором или нет. Если проверка не удалась, мы получаем исключение времени исполнения java.lang.VerifyError.
  • Подготовка (Preparation): JVM выделяет память для переменных класса и инициализирует память значениями по умолчанию.
  • Разрешение (Resolution): это процесс замены символьных ссылок типа непосредственными ссылками. Это делается путем поиска в области метода, чтобы найти ссылку на объект.

Инициализация (Initialization): на этом этапе всем статическим переменным присваиваются их значения, определенные в коде и статическом блоке (если есть). Это выполняется сверху вниз в классе и от родителя к потомку в иерархии классов.

В общем, есть три загрузчика классов:

  • Загрузчик классов начальной загрузки (Bootstrap class loader): каждая реализация JVM должна иметь загрузчик классов начальной загрузки, способный загружать доверенные классы. Он загружает основные классы API Java, представленные в каталоге JAVA_HOME/jre/lib. Этот путь обычно известен как путь начальной загрузки. Он реализован на нативных языках, таких как C, C++.
  • Загрузчик классов расширения (Extension class loader): это дочерний элемент загрузчика классов начальной загрузки. Он загружает классы, представленные в каталогах расширений JAVA_HOME/jre/lib/ext(путь расширения) или в любом другом каталоге, указанном системным свойством java.ext.dirs. Он реализован в Java с помощью класса sun.misc.Launcher$ExtClassLoader.
  • Загрузчик классов системы/приложения (System/Application class loader): это дочерний элемент загрузчика классов расширения. Он отвечает за загрузку классов из пути к классам приложения. Он внутренне использует переменную среды, которая сопоставлена ​​с java.class.path. Он также реализован в Java классом sun.misc.Launcher$AppClassLoader.

// Java код для демонстрации подсистемы загрузчика классов
public class TestClass { 
    public static void main(String[] args) { 
        // класс String загружается загрузчиком начальной загрузки и
        // загрузчик начальной загрузки не является объектом Java, 
        // следовательно, null
        System.out.println(String.class.getClassLoader()); 
  
        // Класс TestClass загружается загрузчиком приложений
        System.out.println(TestClass.class.getClassLoader()); 
    } 
}     

Вывод:

null
sun.misc.Launcher$AppClassLoader@6d06d69c    

Примечание: JVM следует принципу делегирования-иерархии для загрузки классов. Загрузчик классов системы делегирует запрос на загрузку в загрузчик классов расширения и загрузчик классов расширения делегирует запрос в загрузчик класса начальной загрузки. Если класс найден в пути начальной загрузки, класс загружается, в противном случае запрос снова передается загрузчику классов расширения, а затем загрузчику классов системы. Наконец, если загрузчик классов системы не может загрузить класс, мы получаем исключение java.lang.ClassNotFoundException во время выполнения.

Память JVM

Область метода: в области метода хранится вся информация уровня класса, такая как имя класса, имя непосредственного родительского класса, информация о методах и переменных и т. д., включая статические переменные. В JVM есть только одна область методов, и это общий ресурс.

Область кучи (heap): информация обо всех объектах хранится в области кучи. Существует также одна область кучи на JVM. Это также общий ресурс.

Область стека: для каждого потока (thread) JVM создает один стек времени исполнения, который хранится здесь. Каждый блок этого стека называется активационной записью/кадром стека, в котором хранятся вызовы методов. Все локальные переменные этого метода хранятся в соответствующем кадре. После завершения потока стек его выполнения будет уничтожен JVM. Это не общий ресурс.

Регистры компьютера: хранить адрес текущей инструкции исполнения потока. Очевидно, что каждый поток имеет отдельные регистры компьютера.

Стеки нативного метода: для каждого потока создается отдельный нативный стек. Он хранит информацию о нативных методах.

Среда исполнения

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

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

Нативный интерфейс Java (JNI)

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

Библиотеки нативных методов

Это коллекция нативных библиотек (C, C++), которые требуются для механизма исполнения.

Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки