Статьи

Архитектура JVM: загрузчик классов JVM и области данных времени выполнения

Привет читатели! В предыдущей статье серии JVM разработчики узнали о виртуальной машине Java (JVM) и ее архитектуре. Этот учебник поможет разработчикам правильно ответить на вопросы по следующим темам:

  • Подсистема ClassLoader
  • Области данных времени выполнения

1. Введение

Прежде чем двигаться дальше, давайте взглянем на виртуальную машину Java и ее основные характеристики.

1.1 Что такое виртуальная машина Java (JVM)?

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

Основная функция JVM — выполнять скомпилированные .class (т.е. байт-код) и генерировать вывод. Обратите внимание , что каждая операционная система имеет свою JVM, но сгенерированный вывод байт-кода одинаков во всех операционных системах. Это означает, что байт-код, сгенерированный в ОС Windows, также может работать в ОС Linux и наоборот, что делает Java независимым от платформы языком.

Рис. 1: Обзор виртуальной машины Java

Рис. 1: Обзор виртуальной машины Java

1.1.1 Что делает JVM?

Виртуальная машина Java выполняет следующие операции:

  • Загрузка необходимых файлов .class и jar
  • Присвоение ссылок и проверка кода
  • Исполнение кода
  • Предоставляет среду выполнения для байт-кода Java

1.1.2 Внутренняя архитектура JVM

На следующей диаграмме показаны ключевые внутренние компоненты виртуальной машины Java, соответствующие спецификации JVM.

Рис. 2: Архитектура виртуальной машины Java

Рис. 2: Архитектура виртуальной машины Java

Компоненты загрузчика классов и областей данных времени выполнения, показанные на рис. 2, поясняются ниже.

1.2 Подсистема ClassLoader

Подсистема загрузчика классов является важным ядром виртуальной машины Java и используется для загрузки / чтения файлов .class и сохранения байт-кода в области метода JVM. Эта подсистема обрабатывает функциональные возможности загрузки динамических классов и выполняет три основные функции, а именно:

  • Загрузка . Этот компонент обрабатывает загрузку файлов .class из аппаратной системы в память JVM и сохраняет двоичные данные (например, полное имя класса, имя непосредственного родительского класса, информацию о методах, переменных, конструкторах и т. Д.). в областях метода. Для каждого загруженного файла .class JVM немедленно создает объект в памяти кучи типа java.lang.class . Помните , что даже если разработчики вызывают класс несколько раз, будет создан только один объект класса. Существует три основных типа загрузчиков классов:
    • Bootstrap или Primordial ClassLoader : этот загрузчик классов отвечает за загрузку внутренних базовых java-классов, присутствующих в rt.jar и других классов, присутствующих в java.lang.* . По умолчанию он доступен для каждой JVM и написан на родных языках C / C ++. Этот загрузчик классов не имеет родителей, и если разработчики вызывают String.class.getClassLoader() , он возвращает null и любой код, основанный на этом, String.class.getClassLoader() NullPointerException в Java.
    • Расширение ClassLoader : Этот загрузчик классов является дочерним классом Primordial classloader и отвечает за загрузку классов из расширенного пути к классам (т.е. jdk\jre\lib\ext ). Он написан на языке Java, и соответствующий файл .classsun.misc.Launcher$ExtClassLoder.class
    • Приложение или системный загрузчик классов. Этот загрузчик классов является дочерним классом загрузчика классов Расширения и отвечает за загрузку классов из системного пути к классам. Он внутренне использует переменную среды CLASSPATH и написан на языке Java. Системный загрузчик классов в JVM реализован с помощью sun.misc.Launcher$AppClassLoader.class
  • Связывание : этот компонент выполняет связывание класса или интерфейса. Поскольку этот компонент включает в себя выделение новых структур данных, он может выбросить OutOfMemoryError и выполнить три важных действия:
    • Проверка : это процесс проверки двоичного представления класса и проверки, является ли созданный файл .class действительным или нет. Этот процесс выполняется верификатором байт-кода, и если сгенерированный файл .class недействителен, VerifyError
    • Подготовка . Это процесс выделения памяти для статических переменных уровня класса или интерфейса и назначения значений по умолчанию.
    • Решение . Это процесс замены символьных ссылок исходными ссылками на память из области метода.
  • Инициализация : этот компонент выполняет заключительную фазу загрузки класса, когда всем статическим переменным присваиваются исходные значения, а статические блоки выполняются от родительского к дочернему классу. Этот процесс требует тщательной синхронизации, поскольку JVM является многопоточным, и некоторые потоки могут пытаться инициализировать один и тот же класс или интерфейс в одно и то же время.
Рис. 3: Обзор подсистемы ClassLoader

Рис. 3: Обзор подсистемы ClassLoader

1.2.1 Как ClassLoader работает в Java?

Classloader в Java работает в трех принципах: делегирование , видимость и уникальность .

Рис. 4: Механизм загрузки классов в Java

Рис. 4: Механизм загрузки классов в Java

  • Делегация : Согласно этому:
    • Всякий раз, когда виртуальная машина сталкивается с классом, JVM проверяет, загружен ли указанный файл .class или нет.
    • Если файл .class уже загружен в область метода, JVM рассмотрит этот класс. Если нет , JVM запрашивает подсистему загрузчика классов для загрузки этого конкретного класса
    • Система загрузчика классов передает запрос загрузчику классов приложения, который, в свою очередь, делегирует этот запрос загрузчику классов расширения. Загрузчик классов Extension снова делегирует этот запрос Первичному загрузчику классов.
    • Первоначальный загрузчик классов будет искать этот класс в начальной загрузке (т.е. jdk\jre\lib\rt.jar ). Если найдено, соответствующий файл .class загружается
    • Если нет, то первоначальный загрузчик классов делегирует запрос загрузчику расширений. Это будет искать класс по пути jdk\jre\lib\ext . Если найдено, соответствующий файл .class загружается
    • Если нет, загрузчик классов расширения делегирует запрос загрузчику классов приложения и будет искать класс в пути к классам приложения. Если он найден, он загружается, иначе разработчики получат ClassNotFoundException во время выполнения.
  • Видимость : в соответствии с этим:
    • Загрузчик классов приложения может видеть классы, загруженные родительскими загрузчиками классов, но не соответствует действительности, т. Е. Если класс загружается системным загрузчиком классов, а затем снова при попытке явно загрузить тот же класс с использованием расширения загрузчика классов, возникнет ClassNotFoundException в во время выполнения. Например:
      1
      2
      3
      4
      5
      // Printing the classloader of this class.
      System.out.println("Test.getClass().getClassLoader()?= " + Test.class.getClassLoader());
                   
      // Trying to explicitly load the class again using the extension classloader.
      Class.forName("com.jcg.classloading.test.Test", true,  Test.class.getClassLoader().getParent());
  • Уникальность : в соответствии с этим:
    • Класс, загруженный родительским загрузчиком классов, не должен снова загружаться дочерним загрузчиком классов

1.2.2 Как классы загружаются в Java?

Загрузчики классов являются иерархическими. Самый первый класс в приложении специально загружается с помощью статического метода main() . Все последующие классы загружаются с помощью методов загрузки статических или динамических классов.

  • Статическая загрузка классов : в этом методе классы загружаются статически через new оператор
  • Динамическая загрузка классов : в этом методе классы загружаются программно с помощью Class.forName() или loadClass() . Разница между ними заключается в том, что первый инициализирует объект после загрузки, а второй только загружает класс, но не инициализирует объект.

1.3 Области данных времени выполнения

Как показано на рис. 5, эта подсистема разделена на пять основных компонентов, т.е.

Рис. 5: Области данных JVM во время выполнения

Рис. 5: Области данных JVM во время выполнения

  • Область метода . Этот компонент содержит данные уровня класса каждого файла .class такие как метаданные, пул постоянной среды выполнения, статические переменные, код для методов и т. Д. В JVM имеется только одна область метода, которая используется всеми классами. Память, выделенная этой области, по умолчанию выделяется JVM или может быть увеличена в соответствии с потребностями вычислений. Следующее исключительное условие связано с этой областью, т.е.
    • Если область метода недоступна для удовлетворения запроса выделения памяти, JVM выдает ошибку OutOfMemory
  • Область кучи : Этот компонент является частью памяти JVM, где хранятся все объекты и соответствующие им переменные экземпляра и массивы. Эта область памяти создается при запуске JVM, и существует только одна область кучи, совместно используемая несколькими потоками, поскольку данные, хранящиеся в этой области, не являются поточно-ориентированными. Если объекты, хранящиеся в памяти кучи, не имеют ссылки, память для этого объекта восстанавливается сборщиком мусора (то есть автоматической системой управления хранением); объекты в этой области никогда не освобождаются явно. Следующее исключительное условие связано с этой областью, т.е.
    • Если для вычисления требуется больше области кучи, чем доступно, JVM выдает ошибку OutOfMemory
  • Область стека : этот компонент снова является частью памяти JVM, где хранятся все временные переменные. Эта область имеет кадры стека и выделяет один кадр для каждого потока. Когда выполнение потока завершено, этот кадр также уничтожается. Область стека является поточно-ориентированной, так как она не является общим ресурсом и разделена на три дочерних объекта, таких как:
    • Массив локальных переменных: эти локальные переменные используются виртуальной машиной для передачи параметров при вызове метода
    • Стек операндов: это место является фактическим местом для выполнения операций во время выполнения метода. Виртуальная машина берет операнд из стека, оперирует с ним и помещает результат обратно в стек операндов. Этот стек также подготавливает аргументы для передачи в метод и даже для получения результатов метода
    • Данные кадра

    Следующее исключительное условие связано с этой областью, т.е.

    • Если для обработки потока требуется стек виртуальной машины, превышающий допустимый предел, JVM выдает ошибку StackOverflow
    • Если стек виртуальной машины недостаточно расширен, JVM выдает ошибку OutOfMemory
  • Регистры ПК (счетчик программ) : этот компонент содержит адрес инструкции JVM, которая в данный момент выполняется. Каждый поток в Java имеет свой собственный регистр ПК для хранения адреса выполняемой в данный момент инструкции
  • Стеки собственного метода : этот компонент написан на другом языке и содержит информацию о собственном методе. Каждый поток в Java имеет отдельный стек собственных методов. Следующее исключительное условие связано с этой областью, т.е.
    • Если для обработки потока требуется собственный стек, превышающий допустимый предел, JVM выдает ошибку StackOverflow
    • Если собственный стек методов недостаточно расширен, JVM выдает ошибку OutOfMemory

Вот и все для этого поста. Счастливого обучения!

2. Вывод

В этом руководстве разработчики рассмотрели компоненты ClassLoader виртуальной машины и области данных времени выполнения. Вы можете скачать образец кода в разделе « Загрузки ».

3. Загрузите исходный код

Это было учебное пособие по компонентам ClassLoader и Runtime Data Areas виртуальной машины.