- PVSM.RU - https://www.pvsm.ru -

Внутренности JVM, Часть 1 — Загрузчик классов

Перевод статьи подготовлен специально для студентов курса «Разработчик Java» [1].

Внутренности JVM, Часть 1 — Загрузчик классов - 1


В этой серии статей я расскажу о том, как работает Java Virtual Machine. Сегодня мы рассмотрим механизм загрузки классов в JVM [2].

Виртуальная машина Java — это сердце экосистемы Java-технологий. Она делает для Java-программ возможность реализации принципа «написано один раз, работает везде» (write once run everywhere). Как и другие виртуальные машины, JVM представляет собой абстрактный компьютер. Основная задача JVM — загружать class-файлы [3] и выполнять содержащийся в них байт-код [4].

В состав JVM входят различные компоненты, такие как загрузчик классов (Classloader) [5], сборщик мусора (Garbage Collector) [6] (автоматическое управление памятью), интерпретатор, JIT [7]-компилятор, компоненты управления потоками. В этой статье рассмотрим загрузчик классов (Class loader).

Загрузчик классов загружает class-файлы как для вашего приложения, так и для Java API. В виртуальную машину загружаются только те class-файлы Java API, которые действительно требуются при выполнении программы.

Байт-код выполняется подсистемой исполнения (execution engine).

Внутренности JVM, Часть 1 — Загрузчик классов - 2

Что такое загрузка классов?

Загрузка классов [8] — это поиск и загрузка типов (классов и интерфейсов) динамически во время выполнения программы. Данные о типах находятся в бинарных class-файлах.

Этапы загрузки классов

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

  • Загрузка (loading) — поиск и импорт бинарных данных для типа по его имени, создание класса или интерфейса из этого бинарного представления.
  • Связывание, линковка (linking) — выполнение верификации, подготовки и, необязательного, разрешения:
    • Верификация (verification) — проверка корректности импортируемого типа.
    • Подготовка (preparation) — выделение памяти для статических переменных класса и инициализация памяти значениями по умолчанию.
    • Разрешение (resolution) — преобразование символьных ссылок типов в прямые ссылки.
  • Инициализация (initialization) — вызов Java-кода, который инициализирует переменные класса их правильными начальными значениями.

Примечание — загрузчик классов, помимо загрузки классов, также отвечает за поиск ресурсов. Ресурс — это некоторые данные (например, “.class” файл, данные конфигурации, изображения), которые идентифицируются с помощью абстрактного пути, разделенного символом «/». Ресурсы обычно упаковываются вместе с приложением или библиотекой для того, чтобы их можно было использовать в коде приложения или библиотеки.

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

Примечание переводчика — в данном разделе описано поведение для java < 9, в java 9+ произошли небольшие изменения, которые описаны ниже.

В Java используется модель делегирования загрузки классов. Основная идея состоит в том, что у каждого загрузчика классов есть “родительский” загрузчик. Когда происходит загрузка класса [8], то загрузчик “делегирует” поиск класса своему родителю, перед тем как искать класс самостоятельно.

Модель делегирования загрузчиков классов представляет собой граф загрузчиков, которые передают друг другу запросы на загрузку. Корнем в этом графе является bootstrap-загрузчик. Загрузчики классов создаются с одним родителем, которому они могут делегировать загрузку, и осуществляют поиск класса в следующих местах:

  • Кэш
  • Родитель
  • Сам загрузчик

Загрузчик классов сначала проверяет, не загружал ли он данный класс ранее. Если это так, то возвращается тот же класс, который возвращался в прошлый раз (класс, хранящийся в кэше). Если нет, то возможность загрузить класс предоставляется родителю. Эти два шага повторяются рекурсивно в глубину. Если родитель возвращает null (или бросает исключение ClassNotFoundException [9]), тогда загрузчик ищет класс самостоятельно.

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

В Java SE Platform API исторически было определено два загрузчика классов:

Bootstrap class loader (базовый, первичный загрузчик) — загружает классы из bootstrap classpath.

System class loader (системный загрузчик) — родительский класс для новых загрузчиков классов и, как правило, загрузчик классов, используемый для загрузки и запуска приложения.

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

Application class loader — обычно используется для загрузки классов приложения из classpath. Также это загрузчик по умолчанию для некоторых модулей JDK, которые содержат утилиты или экспортируют API утилит. (Примечание переводчика: например, jdk.jconsole, jdk.jshell и др)

Platform class loader — загружает выбранные (на основе безопасности / разрешений) модули Java SE и JDK. Например, java.sql.

Bootstrap class loader — загружает основные модули Java SE и JDK.

Эти три встроенных загрузчика классов работают вместе следующим образом:

  • Application class loader сначала ищет именованные модули, определенные для всех встроенных загрузчиков. Если для одного из этих загрузчиков определен подходящий модуль, то этот загрузчик загружает класс. Если в именованном модуле, определенном для одного из этих загрузчиков, класс не найден, тогда application class loader делегирует его родителю. Если класс не найден родителем, то application class loader ищет его в classpath. Классы, найденные в classpath, загружаются как члены безымянного модуля этого загрузчика.
  • Platform class loader выполняет поиск именованных модулей, определенных для всех встроенных загрузчиков. Если подходящий модуль определен для одного из этих загрузчиков, тогда этот загрузчик загружает класс. Если в именованном модуле, определенном для одного из этих загрузчиков, класс не найден, тогда platform class loader делегирует его родителю.
  • Bootstrap class loader выполняет поиск именованных модулей, определенных для него самого. Если класс не найден в именованном модуле, определенном для bootstrap-загрузчика, тогда bootstrap-загрузчик ищет файлы и каталоги, добавленные в bootstrap classpath, с помощью параметра -Xbootclasspath/a (позволяет добавить файлы и каталоги к bootstrap classpath). Классы, найденные в bootstrap classpath, загружаются как члены безымянного модуля этого загрузчика.

Для просмотра встроенных загрузчиков классов можно использовать следующий код:

package ru.deft.homework;

import java.sql.Date;

public class BuiltInClassLoadersDemo {

   public static void main(String[] args) {
       BuiltInClassLoadersDemo demoObject = new BuiltInClassLoadersDemo();
       ClassLoader applicationClassLoader = demoObject.getClass().getClassLoader();
       printClassLoaderDetails(applicationClassLoader);

       // java.sql classes are loaded by platform classloader
       java.sql.Date now = new Date(System.currentTimeMillis());
       ClassLoader platformClassLoder = now.getClass().getClassLoader();
       printClassLoaderDetails(platformClassLoder);

       // java.lang classes are loaded by bootstrap classloader
       ClassLoader bootstrapClassLoder = args.getClass().getClassLoader();
       printClassLoaderDetails(bootstrapClassLoder);
   }

   private static void printClassLoaderDetails(ClassLoader classLoader) {
       // bootstrap classloader is represented by null in JVM
       if (classLoader != null) {
           System.out.println("ClassLoader name : " + classLoader.getName());
           System.out.println("ClassLoader class : " + classLoader.getClass().getName());
       } else {
           System.out.println("Bootstrap classloader");
       }
   }
}

Запустив этот код на установленном у меня Amazon Corretto 11.0.3, получим следующий результат:

ClassLoader name : app
ClassLoader class : jdk.internal.loader.ClassLoaders$AppClassLoader
ClassLoader name : platform
ClassLoader class : jdk.internal.loader.ClassLoaders$PlatformClassLoader
Bootstrap classloader

Подробнее изучить ClassLoader API вы можете здесь (JDK 11) [10].

Автор: MaxRokatansky

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/330734

Ссылки в тексте:

[1] «Разработчик Java»: https://otus.pw/9Skf/

[2] JVM: https://javarevisited.blogspot.com/2011/12/jre-jvm-jdk-jit-in-java-programming.html

[3] class-файлы: https://en.wikipedia.org/wiki/Java_class_file

[4] байт-код: https://en.wikipedia.org/wiki/Java_bytecode

[5] загрузчик классов (Classloader): https://javarevisited.blogspot.com/2012/12/how-classloader-works-in-java.html#axzz5Y4KhSOWu

[6] сборщик мусора (Garbage Collector): https://javarevisited.blogspot.com/2011/04/garbage-collection-in-java.html#axzz4zt6jlTWS

[7] JIT: http://javarevisited.blogspot.sg/2011/12/jre-jvm-jdk-jit-in-java-programming.html

[8] Загрузка классов: http://javarevisited.blogspot.sg/2012/07/when-class-loading-initialization-java-example.html#axzz4uMIMWleJ

[9] ClassNotFoundException: https://javarevisited.blogspot.com/2013/01/spring-javalangclassnotfoundexception-springframeworkwebcontextloaderlistener.html

[10] здесь (JDK 11): https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html

[11] Источник: https://habr.com/ru/post/468193/?utm_campaign=468193&utm_source=habrahabr&utm_medium=rss