Перейти к содержимому

Java classloader как работает

  • автор:

Подробно про ClassLoader в Java – примеры

Работая на Java, мы часто используем большое количество классов. Они не загружаются все сразу в память, вместо этого загружаются, когда требуется приложением. Тогда и требуется ClassLoaders.

Что такое ClassLoader в Java?

ClassLoader в Java вызывается средой выполнения для динамической загрузки классов, когда это требуется приложением на виртуальной машине. Поскольку ClassLoaders являются частью Java Runtime Environment, виртуальная машина не будет иметь никакого представления о базовых файлах и файловых системах.

Типы

  • Расширение (Extension ClassLoader).
  • Приложение или Система (Application or System ClassLoader).
  • Загрузчик (Bootstrap ClassLoader).

Расширение

Как следует из названия, загружает расширения основных классов Java из библиотеки расширений JDK. Он является дочерним элементом загрузчика Bootstrap ClassLoader и загружает расширения из каталога JRE / lib / text или любого другого каталога, указанного в системном свойстве java.ext.dirs.

Приложение или система

Является дочерним по отношению к Extension ClassLoader. Этот тип загружает все классы уровня приложения, найденные в параметре командной строки -cp или в переменной среды CLASSPATH.

Загрузчик

Как все мы знаем, что Java-классы загружаются экземпляром java.lang.ClassLoade. Но поскольку ClassLoaders являются классами, Bootstrap ClassLoader отвечает за загрузку внутренних классов JDK. По сути, это машинный код, который запускает операцию, когда JVM вызывает ее и загружает классы из rt.jar. Таким образом, вы можете понять, что служба Bootstrap ClassLoader не имеет родительского ClassLoader и поэтому известна как Primordial ClassLoader.

Примечание. Приоритет Bootstrap выше, чем Extension, а приоритет, присвоенный Extension ClassLoader, выше, чем Application ClassLoader. Обратитесь к изображению ниже:

Типы загрузчика классов

Принципы

Набор правил, на основе которых работает ClassLoader, состоит из следующих трех принципов:

  • Свойство уникальности.
  • Модель делегирования.
  • Принцип видимости.

Свойство уникальности

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

Модель делегирования

ClassLoader работает на основе набора операций, заданных моделью делегирования. Таким образом, всякий раз, когда генерируется запрос на поиск класса или ресурса, экземпляр ClassLoader делегирует поиск класса или ресурса родительскому ClassLoader.

Набор операций, на основе которых работает ClassLoader:

  • Виртуальная машина проверяет, загружен ли класс или нет, всякий раз, когда он сталкивается с классом.
  • В случае, когда класс загружен, JVM продолжает выполнение класса, но когда класс не загружен, JVM просит подсистему ClassLoader загрузить этот конкретный класс. После этого подсистема передает управление Application ClassLoader.
  • Затем Application делегирует запрос Extension, которое затем передает запрос Bootstrap .
  • Теперь Bootstrap ищет в пути Bootstrap classpath, чтобы проверить, доступен ли класс или нет. Если класс доступен, он загружается, в противном случае запрос снова передается в Extension.
  • Extension проверяет класс в расширении classpath. Если класс доступен, он загружается, в противном случае запрос снова передается в Application.
  • Наконец, Application ищет класс в пути к классам приложения. Если класс доступен, то загружается, иначе вы увидите исключение ClassNotFoundException.

работа classloader

Обратитесь к изображению ниже.

Принцип видимости

Согласно этому принципу, дочерние классы видны для классов, загруженных их родительскими ClassLoaders, но наоборот не соответствует действительности. Таким образом, классы, загруженные Application, имеют видимость классов, загруженных Extension и Bootstrap.

Например, если у нас есть два класса: A и B, предположим, что класс A загружается Application, а класс B загружается Extension. Здесь классы A и B видны всем тем классам, которые загружены Application, но класс B виден только тем классам, которые загружены Extension.

Кроме того, если вы попытаетесь загрузить эти классы с помощью Bootstrap ClassLoader, вы увидите java.lang.ClassNotFoundException исключение.

Методы

Вот несколько основных методов:

  • loadClass (имя строки, логическое разрешение);
  • defineClass();
  • findClass (Строковое имя);
  • Class.forName (имя строки, логическая инициализация, загрузчик ClassLoader);
  • getParent();
  • getResource().

loadClass()

Этот метод является точкой входа ClassLoader и используется для загрузки класса, на который ссылается JVM. Он принимает имя класса в качестве параметра. JVM вызывает метод loadClass() для разрешения ссылок на класс, устанавливая логическое значение true. Только если нам нужно определить, существует ли класс или нет, логический параметр имеет значение false.

public Class loadClass(String name, boolean resolve) throws ClassNotFoundException 

defineClass()

Последний метод, используемый для определения массива байтов как экземпляра класса. В случае, если класс является недействительным, он выдает ClassFormatError.

protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

findClass ()

Метод используется для поиска указанного класса. Таким образом, он просто находит класс с полностью определенным именем в качестве параметра, но не загружает его. Метод loadClass() вызывает этот метод, если родительский ClassLoader не может найти запрошенный класс. Кроме того, если ни один из родителей ClassLoader не находит класс, реализация по умолчанию генерирует исключение ClassNotFoundException.

protected Class findClass(String name) throws ClassNotFoundException

Class.forName ()

Этот метод используется для загрузки и инициализации класса. Это дает возможность выбрать любой из ClassLoaders и в случае, если параметр ClassLoader равен NULL, тогда автоматически используется Bootstrap ClassLoader.

public static Class forName(String name, boolean initialize, ClassLoader loader)throws ClassNotFoundException

GetParent()

Метод getParent используется для возврата родительского ClassLoader для делегирования.

public final ClassLoader getParent()

getResource()

Метод getResource() пытается найти ресурс с заданным именем. Первоначально он делегирует запрос родительскому ClassLoader для ресурса. В случае, если родительский элемент является нулевым, ищется путь к ClassLoader, встроенному в JVM.

Теперь, если это не удается, тогда метод вызовет findResource (), чтобы найти ресурс, где имя ресурса указывается как вход, который может быть как абсолютным, так и относительным путем к классу. Затем он возвращает объект URL для чтения ресурса или возвращает нулевое значение, если у ресурса нет соответствующих прав для возврата ресурса или он не найден.

public URL getResource(String name)

Пользовательский ClassLoader в Java

Встроенные ClassLoaders позаботятся о большинстве случаев, когда файлы уже находятся в файловой системе, но если вы хотите загрузить классы с локального жесткого диска, вам нужно использовать пользовательские ClassLoaders.

Как создать

Чтобы создать, вам нужно расширить класс ClassLoader и переопределить метод findClass().

Пример: Давайте создадим собственный ClassLoader, который расширяет стандартный ClassLoader и загружает байтовый массив из указанного файла.

package edureka; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; public class Sample extends ClassLoader < @Override public Class findClass(String samplename) throws ClassNotFoundException < byte[] b = customLoadClassFromFile(samplename); return defineClass(samplename, b, 0, b.length); >private byte[] customLoadClassFromFile(String demofilename) < InputStream inStream = getClass().getClassLoader().getResourceAsStream( demofilename.replace('.', File.separatorChar) + ".class"); byte[] buffer; ByteArrayOutputStream bStream = new ByteArrayOutputStream(); int nextValue = 0; try < while ( (nextValue = inStream.read()) != -1 ) < bStream.write(nextValue); >> catch (IOException e) < e.printStackTrace(); >buffer = bStream.toByteArray(); return buffer; > >

ClassLoader в Java

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

Типы

  • Расширение ClassLoader (Extension).
  • Приложение или Система ClassLoader (Application or System).
  • Загрузчик ClassLoader (Bootstrap).

Расширение

Загружает расширения основных классов Java из библиотеки расширений JDK. Он является дочерним элементом Bootstrap и загружает расширения из каталога JRE / lib / text или любого другого каталога, указанного в системном свойстве java.ext.dirs.

Приложение или Система

Является дочерним по отношению к Extension. Этот тип загружает все классы уровня приложения, найденные в параметре командной строки -cp или в переменной среды CLASSPATH.

Загрузчик

Как все мы знаем, что Java-классы загружаются экземпляром java.lang.ClassLoade. Но поскольку ClassLoaders являются классами, Bootstrap отвечает за загрузку внутренних классов JDK. Представляет собой машинный код, который запускает операцию, когда JVM вызывает ее и загружает классы из rt.jar. Таким образом, вы можете понять, что служба Bootstrap не имеет родительского ClassLoader и, таким образом, называется Primordial ClassLoader.

Примечание. Приоритет Bootstrap выше, чем Extension, а приоритет, присвоенный Extension ClassLoader, выше, чем Application ClassLoader. Обратитесь к изображению ниже:

Типы загрузчика классов

Принципы

Набор правил, на основе которых работает Java ClassLoader, состоит из следующих трех принципов:

  • Свойство уникальности.
  • Модель делегирования.
  • Принцип видимости.

Свойство уникальности

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

Модель делегирования

ClassLoader работает на основе набора операций, заданных моделью делегирования. Таким образом, всякий раз, когда генерируется запрос на поиск класса или ресурса, экземпляр ClassLoader делегирует поиск класса или ресурса родительскому ClassLoader.

Набор операций, на основе которых работает ClassLoader:

  • Виртуальная машина проверяет, загружен ли класс или нет, всякий раз, когда она сталкивается с классом.
  • В случае, если класс загружен, JVM продолжает выполнение класса, но в сценарии, где класс не загружен, JVM просит подсистему ClassLoader загрузить этот конкретный класс. После этого подсистема ClassLoader передает управление приложению ClassLoader.
  • Затем приложение ClassLoader делегирует запрос расширению ClassLoader, которое затем передает запрос загрузчику ClassLoader.
  • Теперь загрузчик ClassLoader ищет в пути своем classpath, чтобы проверить, доступен ли класс или нет. Если класс доступен, он загружается, в противном случае запрос снова передается в расширение.
  • Расширение проверяет класс в расширении classpath. Если класс доступен, он загружается, в противном случае запрос снова передается в приложение ClassLoader.
  • Наконец, приложение ClassLoader ищет класс в пути к классам приложения. Если класс доступен, то загружается, иначе вы увидите исключение ClassNotFoundException.

работа ClassLoader

Обратитесь к изображению ниже.

Принцип видимости

Согласно этому принципу, дочерние классы видны для классов, загруженных их родительскими ClassLoaders, но наоборот не соответствует действительности. Таким образом, классы, загруженные приложением ClassLoader, имеют видимость классов, загруженных расширением и загрузчиком ClassLoader.

Например, если у нас есть два класса: AB, предположим, что класс A загружается приложением ClassLoader, а класс B загружается расширением ClassLoader. Здесь классы A и B видны всем тем классам, которые загружены приложением ClassLoader, но класс B виден только тем классам, которые загружены расширением ClassLoader.

Кроме того, если вы попытаетесь загрузить эти классы с помощью загрузчика ClassLoader, вы увидите. исключение.

Методы

Вот несколько основных методов ClassLoader:

  • loadClass (имя строки, логическое разрешение);
  • defineClass();
  • findClass (Строковое имя);
  • Class.forName (имя строки, логическая инициализация, загрузчик ClassLoader);
  • GetParent();
  • getResource().

loadClass (имя строки, логическое разрешение)

Этот метод является точкой входа ClassLoader и используется для загрузки класса, на который ссылается JVM. Он принимает имя класса в качестве параметра. JVM вызывает метод loadClass() для разрешения ссылок на класс, устанавливая логическое значение true. Только если нам нужно определить, существует ли класс или нет, логический параметр имеет значение false.

public Class loadClass(String name, boolean resolve) throws ClassNotFoundException 

defineClass()

Последний метод, используемый для определения массива байтов как экземпляра класса. В случае, если класс недопустим, он выдает ClassFormatError.

protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

findClass (Строковое имя)

Используется для поиска указанного класса. Таким образом, он просто находит класс с полностью определенным именем в качестве параметра, но не загружает класс. Метод loadClass() вызывает этот метод, если родительский ClassLoader не может найти запрошенный класс. Кроме того, если ни один из родителей ClassLoader не находит класс, реализация по умолчанию генерирует исключение ClassNotFoundException.

protected Class findClass(String name) throws ClassNotFoundException

Class.forName (имя строки, логическая инициализация, загрузчик ClassLoader)

Этот метод используется для загрузки и инициализации класса. Это дает возможность выбрать любой из ClassLoaders и в случае, если параметр ClassLoader равен NULL, тогда автоматически используется Bootstrap ClassLoader.

public static Class forName(String name, boolean initialize, ClassLoader loader)throws ClassNotFoundException

GetParent()

Используется для возврата родительского ClassLoader для делегирования.

public final ClassLoader getParent()

getResource()

Пытается найти ресурс с заданным именем. Первоначально он делегирует запрос родительскому ClassLoader для ресурса. Если родительский объект имеет значение null, то ищется путь к ClassLoader, встроенному в JVM. Теперь, если это не удается, тогда метод вызовет findResource(String), чтобы найти ресурс, где имя ресурса указывается в качестве входных данных, которые могут быть как абсолютным, так и относительным путем к классу. Затем он возвращает объект URL для чтения ресурса или возвращает нулевое значение, если у ресурса нет достаточных прав для возврата ресурса или он не найден.

public URL getResource(String name)

Пользовательский

Встроенные ClassLoaders позаботятся о большинстве случаев, когда файлы уже находятся в файловой системе, но если вы хотите загрузить классы с локального жесткого диска, вам нужно использовать пользовательские ClassLoaders.

Чтобы создать собственный ClassLoader, вам нужно расширить класс ClassLoader и переопределить метод findClass():

package edureka; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; public class Sample extends ClassLoader < @Override public Class findClass(String samplename) throws ClassNotFoundException < byte[] b = customLoadClassFromFile(samplename); return defineClass(samplename, b, 0, b.length); >private byte[] customLoadClassFromFile(String demofilename) < InputStream inStream = getClass().getClassLoader().getResourceAsStream( demofilename.replace('.', File.separatorChar) + ".class"); byte[] buffer; ByteArrayOutputStream bStream = new ByteArrayOutputStream(); int nextValue = 0; try < while ( (nextValue = inStream.read()) != -1 ) < bStream.write(nextValue); >> catch (IOException e) < e.printStackTrace(); >buffer = bStream.toByteArray(); return buffer; > >

Средняя оценка 4.2 / 5. Количество голосов: 11

Спасибо, помогите другим - напишите комментарий, добавьте информации к статье.

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

Напишите комментарий, что можно добавить к статье, какой информации не хватает.

Как ClassLoader работает на Java?

Java Classloader является частью JRE (Java Runtime Environment), которая динамически загружает классы Java в JVM (виртуальная машина Java). В частности, Java-программа, в отличие от написанной на C или C ++, не является одним исполняемым файлом, а состоит из нескольких отдельных файлов классов, каждый из которых соответствует одному классу Java. Обычно классы загружаются только по требованию. Это означает, что эти файлы классов Java не загружаются сразу в память, а загружаются по требованию, как это требуется программе (Class Loader). Class Loader - это компонент с Java Execution Engine, который загружает двоичные данные из файлов .class, доступных в пути класса, в область методов. Загрузка класса в область метода происходит только в первый раз, когда класс ссылается в запущенном приложении Java. Для всех других ссылок данные повторно используются из области метода, если класс не был РАЗГРУЗЛЕН.

ClassLoader в Java работает по трем принципам:

  1. Делегирование
  2. Видимость
  3. Уникальность

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

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

  1. Загрузочный загрузчик класса Bootstrap
  2. загрузчик классов расширений
  3. загрузчик системного класса

Загрузочный загрузчик класса bootstrap загружает внутренние классы JDK, обычно загружает rt.jar и другие базовые классы, например Java.lang. * Классы пакетов. Класс загрузчика классов расширений загружает классы из каталога расширений JDK, обычно $ JAVA_HOME / lib / ext каталога. Он реализуется классом sun.misc.Launcher $ ExtClassLoader. Системный загрузчик классов загружает код, найденный на Java.class.path, который сопоставляется с переменной среды CLASSPATH. Это реализуется классом sun.misc.Launcher $ AppClassLoader.

Создание простого ClassLoader

Загрузчик классов начинается с того, что является подклассом Java.lang.ClassLoader. Единственным абстрактным методом, который должен быть реализован, является loadClass(). Поток loadClass() выглядит следующим образом:

Загрузчик классов начинается с того, что является подклассом Java.lang.ClassLoader. Единственным абстрактным методом, который должен быть реализован, является loadClass(). Поток loadClass() выглядит следующим образом.

Погрузчики классов являются иерархическими. Начальный класс загружается с помощью общедоступного метода static main(), объявленного в вашем классе. Все последующие загруженные классы загружаются классами, которые уже загружены и запущены.

Иерархия загрузчика классов

Большинство программистов Java никогда не должны явно использовать загрузчики классов (за исключением загрузки ресурсов, чтобы они все еще работали, когда они были объединены в JAR), не говоря уже о написании собственных. ClassLoaders используются в очень больших системах и серверных приложениях, чтобы делать такие вещи, как:

Class Loaders являются функциональным компонентом виртуальной машины Java, которая загружает данные класса из файла .class или из сети в область «Метод» в «Кучах». Каждый загрузчик классов имеет собственное пространство имен, а классы, вызываемые определенным загрузчиком классов, попадают в его пространство имен. Классы, вызываемые двумя разными загрузчиками классов, не будут иметь видимости друг над другом, что приведет к повышению безопасности. Механизм делегирования родительского дочернего элемента класса загрузчика гарантирует, что классы Java api никогда не могут быть взломаны несанкционированным кодом. Это связано с тем, что существуют загрузчики классов, время выполнения Java не должно ничего знать о файлах и файловых системах при запуске Java-программ. Кроме того, Java ClassLoader написан на языке Java. Это означает, что легко создать собственный ClassLoader, не понимая более тонкие детали JVM (виртуальная машина Java).

Как происходит загрузка классов в JVM

Java-университет

После того, как самая сложная часть в работе программиста выполнена и приложение «Hello World 2.0» написано, осталось собрать дистрибутив и передать его заказчику, ну или хотя бы в службу тестирования. В дистрибутиве у нас все как полагается и, запуская нашу программу, на сцену выходит Java Virtual Machine. Ни для кого не секрет, что виртуальная машина считывает команды, представленные в class-файлах в виде байт-кода и транслирует их в виде инструкций процессору. Предлагаю немного разобраться в схеме попадания байт-кода в виртуальную машину.

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

Как происходит загрузка классов в JVM - 1

Используется для поставки в JVM скомпилированного байт-кода, который, как правило, хранится в файлах с расширением .class , но может быть также получен из других источников, например, загружен по сети или же сгенерирован самим приложением. Согласно спецификации Java SE для того, чтобы получить работающий в JVM код, необходимо выполнить три этапа:

  • загрузка байт-кода из ресурсов и создание экземпляра класса Class сюда входит поиск запрошенного класса среди загруженных ранее, получение байт-кода для загрузки и проверка его корректности, создание экземпляра класса Class (для работы с ним в runtime), загрузка родительских классов. Если родительские классы и интерфейсы не были загружены, то и рассматриваемый класс считается не загруженным.
  • связывание (или линковка) по спецификации этот этап разбивается еще на три стадии:
    • Verification, происходит проверка корректности полученного байт-кода.
    • Preparation, выделение оперативной памяти под статические поля и инициализация их значениями по умолчанию (при этом явная инициализация, если она есть, происходит уже на этапе инициализации).
    • Resolution, разрешение символьных ссылок типов, полей и методов.
  • инициализация полученного объекта здесь, в отличие от предыдущих пунктов, вроде бы все понятно, что должно происходить. Было бы, конечно, интересно разобраться как именно это происходит.

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

  • Класс должен быть полностью загружен прежде, чем слинкован.
  • Класс должен быть полностью проверен и подготовлен прежде, чем проинициализирован.
  • Ошибки разрешения ссылок происходят во время выполнения программы, даже если были обнаружены на этапе линковки.

Как известно, в Java реализована отложенная (или ленивая) загрузка классов. А это значит, что загрузка классов ссылочных полей загружаемого класса не будет выполняться до тех пор, пока в приложении не встретится явное к ним обращение. Иными словами, разрешение символьных ссылок не обязательно и по умолчанию не происходит. Тем не менее, в реализации JVM может использоваться и энергичная загрузка классов, т.е. все символьные ссылки должны быть учтены сразу. Вот для этого пункта и действует последнее требование. Еще стоит заметить, что разрешение символьных ссылок не привязано ни к какому из этапов загрузки класса. В общем каждый из этих этапов тянет на неплохое такое исследование, попробуем разобраться с первым, а именно загрузкой байт-кода.

Типы загрузчиков Java

  1. Bootstrap – базовый загрузчик, также называется Primordial ClassLoader. загружает стандартные классы JDK из архива rt.jar
  2. Extension ClassLoader – загрузчик расширений. загружает классы расширений, которые по умолчанию находятся в каталоге jre/lib/ext, но могут быть заданы системным свойством java.ext.dirs
  3. System ClassLoader – системный загрузчик. загружает классы приложения, определенные в переменной среды окружения CLASSPATH

Абстрактный класс ClassLoader

Каждый загрузчик, за исключением базового, является потомком абстрактного класса java.lang.ClassLoader . Например, реализацией загрузчика расширений является класс sun.misc.Launcher$ExtClassLoader , а системного загрузчика – sun.misc.Launcher$AppClassLoader . Базовый загрузчик является нативным и его реализация включена в JVM. Любой класс, который расширяет java.lang.ClassLoader , может предоставить свой способ загрузки классов с блэк-джеком и этими самыми. Для этого необходимо переопределить соответствующие методы, которые на данный момент я могу рассмотреть только поверхностно, т.к. не разбирался детально в этом вопросе. Вот они:

 package java.lang; public abstract class ClassLoader < public ClassloadClass(String name); protected Class loadClass(String name, boolean resolve); protected final Class findLoadedClass(String name); public final ClassLoader getParent(); protected Class findClass(String name); protected final void resolveClass(Class c); > 

loadClass(String name) один из немногих публичных методов, который и является точкой входа для загрузки классов. Его реализация сводится к вызову другого protected метода loadClass(String name, boolean resolve) , его и необходимо переопределить. Если посмотреть Javadoc этого защищенного метода, то можно понять примерно следующее – на вход подаются два параметра. Один это бинарное имя класса (или полностью определенное имя класса), который необходимо загрузить. Имя класса указывается с перечислением всех пакетов. Второй параметр – это флаг, определяющий, требуется ли выполнять процедуру разрешения символьных ссылок. По умолчанию он равен false, что означает использование ленивой загрузки классов. Далее, согласно документации, в реализации метода по умолчанию происходит вызов findLoadedClass(String name) , который проверяет был ли класс уже загружен ранее и если это так, вернет ссылку на этот класс. Иначе будет вызван метод загрузки класса у родительского загрузчика. Если ни один из загрузчиков не смог найти загруженный класс, каждый из них, следуя в обратном порядке, попытается этот класс найти и загрузить, переопределяя метод findClass(String name) . Подробнее об этом будет рассмотрено в главе «Схема загрузки классов». И наконец, в последнюю очередь, после того как класс удалось загрузить, в зависимости от флага resolve будет решено стоит ли выполнять загрузку классов по символьным ссылкам. Явный пример того, что стадия Resolution может быть вызвана на этапе загрузки класса. Соответственно, расширяя класс ClassLoader и переопределяя его методы, пользовательский загрузчик может осуществлять свою логику поставки байт-кода в виртуальную машину. Также в Java поддерживается понятие «текущего» загрузчика классов. Текущий загрузчик это тот, который загрузил класс, выполняющийся в данный момент. Каждый класс знает, каким загрузчиком он был загружен, и можно получить эту информацию, вызвав у него метод String.class.getClassLoader() . Для всех классов приложения «текущий» загрузчик, как правило, системный.

Три принципа загрузки классов

  • Делегирование Запрос на загрузку класса передается родительскому загрузчику, и попытка загрузить класс самостоятельно выполняется, только если родительский загрузчик не смог найти и загрузить класс. Такой подход позволяет загружать классы тем загрузчиком, который максимально близко находится к базовому. Так достигается максимальная область видимости классов. Каждый загрузчик ведет учет классов, которые были загружены именно им, помещая их в свой кэш. Множество этих классов и называется областью видимости.
  • Видимость Загрузчик видит только «свои» классы и классы «родителя» и понятия не имеет о классах, которые были загружены его «потомком».
  • Уникальность Класс может быть загружен только однажды. Механизм делегирования позволяет убедиться, что загрузчик, инициирующий загрузку класса, не перегрузит загруженный ранее в JVM класс.

Схема загрузки классов

Когда происходит вызов загрузки какого-либо класса, происходит поиск этого класса в кэше уже загруженных классов текущего загрузчика. Если желаемый класс еще не загружался ранее, по принципу делегирования управление передается родительскому загрузчику, который находится по иерархии на уровень выше. Родительский загрузчик также пытается найти желаемый класс у себя в кэше. Если класс уже был загружен и загрузчик знает о его местонахождении, то будет возвращен объект Class этого класса. Если нет, поиск будет продолжаться до тех пор, пока не дойдет до базового загрузчика. Если и в базовом загрузчике нет информации об искомом классе (т.е. он еще не был загружен), будет выполнен поиск байт-кода этого класса по расположению классов, о котором знает данный загрузчик, и, если загрузить класс не удастся, управление вернется обратно загрузчику-потомку, который будет пытаться выполнить загрузку из известных ему источников. Как уже упоминалось выше, расположение классов для базового загрузчика это библиотека rt.jar, для загрузчика расширений – каталог с расширениями jre/lib/ext, для системного – CLASSPATH, для пользовательского это может быть что-то свое. Таким образом, ход загрузки классов идет в обратном направлении - от корневого загрузчика до текущего. Когда байт-код класса найден, происходит загрузка класса в JVM и получение экземпляра типа Class . Как нетрудно заметить, описанная схема загрузки похожа на приведенную выше реализацию метода loadClass(String name) . Ниже можно рассмотреть эту схему на диаграмме.

Как происходит загрузка классов в JVM - 2

В качестве заключения

На первых шагах изучения языка нет какой-то особой необходимости в понимании того, как происходит загрузка классов в Java, но знание этих базовых принципов позволит не впадать в отчаяние, встретив такие ошибки, как ClassNotFoundException или NoClassDefFoundError . Ну или хотя бы примерно понимать, в чем корень проблемы. Так исключение ClassNotFoundException возникает при динамической загрузке класса во время выполнения программы, когда загрузчики не могут найти требуемый класс ни в кэше, ни по пути нахождения классов. А вот ошибка NoClassDefFoundError является более критичной и возникает в том случае, когда во время компиляции искомый класс был доступен, но не виден во время выполнения программы. Это может произойти, если в поставку программы забыли включить библиотеку, которую она использует. Ну и сам факт понимания принципов устройства того инструмента, которым пользуешься в работе (не обязательно четкое и детальное погружение в его недра), добавляет некоторую ясность в понимании протекающих внутри этого механизма процессов, что, в свою очередь, ведет к уверенному использованию этого инструмента.

Источники

How ClassLoader Works in Java В целом очень полезный источник с доступным изложением информации. Загрузка классов, ClassLoader Довольно объемная статья, но с уклоном на то, как сделать свою реализацию загрузчика с этими самыми. ClassLoader: динамическая загрузка классов К сожалению, этот ресурс сейчас недоступен, но там я нашел самую понятную диаграмму со схемой загрузки классов, поэтому не могу не добавить. Java SE Specification: Chapter 5. Loading, Linking, and Initializing

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *