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

Материал из Циклопедии
Перейти к навигации Перейти к поиску

Загрузка классов — процесс считывания и анализа байт-кода скомпилированного java-класса и построение на его основе объекта класса java.lang.Class.

В Java реализована динамическая модель загрузки классов, благодаря которой требуемые JVM классы могут загружаться в оперативную память не при запуске виртуальной машины, но по мере необходимости. Механизм, реализующий эту модель, — загрузчики классов, являющиеся обычными Java-объектами.

Иерархия загрузчиков классов[править]

В Java SE имеет место основанная на делегировании иерархия загрузчиков классов. Каждый classloader в иерархии, кроме корневого, хранит ссылку на родительский загрузчик в поле parent объекта ClassLoader. По умолчанию родительским полагается тот загрузчик, который загрузил класс данного загрузчика.

Корнем иерархии является реализованный на уровне JVM bootstrap-загрузчик. При загрузке очередного класса bootstrap-загрузчик проверяет, не был ли данный класс загружен ранее. Если был, загрузчик возвращает созданный уже объект класса java.lang.Class, в противном случае пытается осуществить загрузку. Случается, что искомый класс не был найден, тогда выбрасывается исключение ClassNotFoundException.[1]

Для остальных загрузчиков классов логика реализована по следующей схеме. При попытке загрузить класс с заданным именем, загрузчик проверяет в кэше, не был ли этот класс загружен ранее, и если не был, то делегирует задачу более низкоуровневому загрузчику. Тот может либо успешно вернуть объект запрашиваемого класса, либо выбросить ClassNotFoundException. Объект класса возвращается как результат загрузки, а если поймано исключение, то class loader пытается сам найти и загрузить класс и при успехе возвращает созданный объект, а при неудаче выбрасывает ClassNotFoundException.[1]

По умолчанию JVM использует следующую иерархию:[2]

  • bootstrap-загрузчик. По умолчанию он предназначен для загрузки требуемых для работы JVM классов, в частности, пакетов rt.jar[3] и i18n.jar. Запуск JVM с ключом -Xbootclasspath позволяет переопределить наборы базовых классов. Особенностью этого загрузчика является тот факт, что он недоступен из Java — вызов метода getClassLoader() на загруженном bootstrap классе вернёт null, а обратиться к нему можно только внутри класса java.lang.ClassLoader посредством приватных нативных методов.
  • Extension-загрузчик. Предназначен для загрузки расширений, по умолчанию хранящихся в $JAVA_HOME/lib/ext, впрочем, этот путь может быть изменён с помощью опции java.ext.dirs. Этот загрузчик представлен классом sun.misc.Launcher$ExtClassLoader. В качестве значения поля parent установлено null, что интерпретируется как bootstrap-загрузчик.
  • System-загрузчик, отвечающий за загрузку классов, находящихся в classpath. Этот загрузчик представлен классом sun.misc.Launcher$AppClassLoader и может пыть получен с помощью статического метода java.lang.ClassLoader.getSystemClassLoader(). В качестве значения поля parent хранится ссылка на объект ExtClassLoader.

Подобная схема повышает безопасность работы JVM, поскольку каждый класс загружается наиболее близким к bootstrap загрузчиком. Таким образом это позволит избежать ситуации, в которой требуемый для работы виртуальной машины класс окажется заменён одноименным классом, найденным высокоуровневым загрузчиком.

Кроме того, в Java определено ещё несколько загрузчиков классов, например RMIClassLoader[4] для загрузки классов с помощью RMI, а также предоставлена возможность реализовывать собственные загрузчики.

В Java EE предлагается другой подход, учитывающий вероятность работы многочисленных не связанных между собой приложений на одном сервере. В этой версии реализована более обширная иерархия загрузчиков классов. Спецификация Java Servlet рекомендует, чтобы Web class loader пытался загрузить класс до того, как делегирует эту задачу родительскому загрузчику. Подобное поведение для Sun JRE может быть настроено в конфигурационном файле sun-web.xml с помощью параметра delegate="false" в описании загрузчика классов.[5]

Собственный загрузчик[править]

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

Наиболее простым путём является создание нового класса, который наследуется от java.lang.ClassLoader. В абстрактном классе ClassLoader реализованы все необходимые методы для следования модели делегирования. В частности, это метод defineClass(), который преобразует байт-код в объект java.lang.Class. Таким образом, для корректной работы достаточно переопределить только метод loadClass(String name), который должен загружать байт-код класса в оперативную память и передавать его интерпретацию методу defineClass().[6]

С помощью собственных загрузчиков имеется возможность создать ситуацию, когда один и тот же класс будет загружен одновременно несколькими загрузчиками. Для этого достаточно определить два загрузчика на одном уровне иерархии. Подобные дублированные классы будут рассматриватся JVM как различные и попытка привести объект одного класса к типу другого вызовет ClassCastException. Дело в том, что с точки зрения JVM уникальный идентификатор класса образует пара, состоящая из полного имени класса и загрузчика.[7]

Источники[править]

  1. 1,0 1,1 java.lang.ClassLoader // grepcode
  2. Основы динамической загрузки классов в Java // Санкт-Петербургская группа тестирования JVM
  3. Аббревиатура Rt означает Runtime
  4. Class RMIClassLoader // Oracle
  5. Sun Java System Application Server Platform Edition 9 Developer's Guide // Oracle
  6. Java Reflection — Dynamic Class Loading and Reloading // tutorials.jenkov.com
  7. ClassLoader: динамическая загрузка классов // dmitriymx.ru