Пользователь
0,0
рейтинг
8 сентября 2010 в 22:54

Разработка → Загрузка классов в Java. Теория

JAVA*


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

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


Введение


Любой класс (экземпляр класса java.lang.Class в среде и .class файл в файловой системе), используемый в среде исполнения был так или иначе загружен каким-либо загрузчиком в Java. Для того, чтобы получить загрузчик, которым был загружен класс А, необходимо воспользоваться методом A.class.getClassLoader().

Классы загружаются по мере надобности, за небольшим исключением. Некоторые базовые классы из rt.jar (java.lang.* в частности) загружаются при старте приложения. Классы расширений ($JAVA_HOME/lib/ext), пользовательские и большинство системных классов загружаются по мере их использования.

Виды загрузчиков


Различают 3-и вида загрузчиков в Java. Это — базовый загрузчик (bootstrap), системный загрузчик (System Classloader), загрузчик расширений (Extension Classloader).

Bootstrap — реализован на уровне JVM и не имеет обратной связи со средой исполнения. Данным загрузчиком загружаются классы из директории $JAVA_HOME/lib. Т.е. всеми любимый rt.jar загружается именно базовым загрузчиком. Поэтому, попытка получения загрузчика у классов java.* всегда заканчиватся null'ом. Это объясняется тем, что все базовые классы загружены базовым загрузчиком, доступа к которому из управляемой среды нет.

Управлять загрузкой базовых классов можно с помощью ключа -Xbootclasspath, который позволяет переопределять наборы базовых классов.

System Classloader — системный загрузчик, реализованный уже на уровне JRE. В Sun JRE — это класс sun.misc.Launcher$AppClassLoader. Этим загрузчиком загружаются классы, пути к которым указаны в переменной окружения CLASSPATH.

Управлять загрузкой системных классов можно с помощью ключа -classpath или системной опцией java.class.path.

Extension Classloader — загрузчик расширений. Данный загрузчик загружает классы из директории $JAVA_HOME/lib/ext. В Sun JRE — это класс sun.misc.Launcher$ExtClassLoader.

Управлять загрузкой расширений можно с помощью системной опции java.ext.dirs.

Понятия


Различают текущий загрузчик (Current Classloader) и загрузчик контекста (Context Classloader).

Current Classloader — это загрузчик класса, код которого в данный момент исполняется. Текущий загрузчик используется по умолчанию для загрузки классов в процессе исполнения. В часности, при использовании метода Class.forName("")/ClassLoader.loadClass("") или при любой декларации класса, ранее не загруженного.

Context Classloader — загрузчик контекста текущего потока. Получить и установить данный загрузчик можно с помощью методов Thread.getContextClassLoader()/Thread.setContextClassLoader(). Загрузчик контекста устанавливается автоматически для каждого нового потока. При этом, используется загрузчик родительского потока.

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


Начиная с версии Java 2 Platform, Standard Edition, v1.2 загрузчики классов образуют иерархию. Корневым является базовый (у него предка нет). Все остальные загрузчики при инициализации инстанциируют ссылку на родительский загрузчик. Такая иерархия необходима для модели делегирования загрузки. В общем случа, иерархия выглядит следующим образом.



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



Рассмотрим процесс загрузки более детально. Пусть в систем исполнения встретилась декларация переменной пользовательского класс Student.

1) Системный загрузчик попытается поискать в кеше класс Student.
_1.1) Если класс найден, загрузка окончена.
_1.2) Если класс не найден, загрузка делегируется загрузчику расширений.
2) Загрузчик расширений попытается поискать в кеше класс Student.
_2.1) Если класс найден, загрузка окончена.
_2.2) Если класс не найден, загрузка делегируется базовому загрузчику.
3) Базовый загрузчик попытается поискать в кеше класс Student.
_3.1) Если класс найден, загрузка окончена.
_3.2) Если класс не найден, базовый загрузчик попытается его загрузить.
__3.2.1) Если загрузка прошла успешно, она закончена ;)
__3.2.2) Иначе управление предается загрузчику раширений.
_3.3) Загрузчик расширений пытается загрузить класс.
__3.3.1) Если загрузка прошла успешно, она закончена ;)
__3.3.2) Иначе управление предается системному загрузчику.
_3.4) Системный загрузчик пытается загрузить класс.
__3.4.1) Если загрузка прошла успешно, она закончена ;)
__3.4.2) Иначе генерируется исключение java.lang.ClassNotFoundException.

Если в системе присутствуют пользовательские загрузчики, они должны
а) расширять класс java.lang.ClassLoader;
б) поддерживать модель динамической загрузки.

Inside


Запустим простейшее приложениие с ключем -verbose:class.

public class A { }

public class B extends A { }

public class C extends B { }

public class Main {
  
  public static void main(String args[]) {
    C c = new C();
    B b = new B();
    A a = new A();
  }
}

* This source code was highlighted with Source Code Highlighter.


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

[Loaded Main from file:/C:/devel/CL/bin/]
[Loaded A from file:/C:/devel/CL/bin/]
[Loaded B from file:/C:/devel/CL/bin/]
[Loaded C from file:/C:/devel/CL/bin/]

Заключение


В следющей статье будет интерестнее ;)
Костюков Владимир @spiff
карма
240,0
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (28)

  • +4
    Базовые классы Java (rt.jar) загружаются при старте приложения. Классы расширений ($JAVA_HOME/lib/ext), также загружаются при старте.

    Это неправда. Не думаете же Вы в самом деле, что все 6800+ классов из rt.jar загружаются сразу же при старте?
    Системные классы также загружаются по мере необходимости. В случаях, когда используется Class Data Sharing (-Xshare:on), некоторые классы (перечисленные в jre/lib/classlist) могут быть предзагружены путем отображения (mmap) файла classes.jsa в виртуальную память.
    • +2
      Согласен. Сейчас напишу более корректно ;)
      • 0
        Этим загрузчиком загружаются классы из меременной CLASSPATH

        Надо написать, что загрузчик грузит классы указанные в переменной CLASSPATH. Сами то классы хранятся не в переменной :)
        • 0
          Тогда классы, путь к которым указан в переменной CLASSPATH
        • 0
          Тогда уж «грузит классы, указанные в переменной...». И ещё вместо «указанные» написал бы «перечисленные».
          • 0
            Спс, за замечания ;) Кое-что исправил.
  • +1
    Может, стоит упомянуть:
    1. Когда загружется класс
    2. Что происходит при загрузке (про инициализацию статиков и т.п.)
    3. Как работает память, в которой лежат загруженные классы
    • +1
      Во-во. Честно говоря, статья получилась ни о чем.
      Ни про URLClassLoader или зачем вообще нужны пользовательские загрузчики, ни про выгрузку классов, ни про взаимоотношения class loading / class linking / class initialization, ни про Class Data Sharing.
      Ну, разве что в следующей статье, которая обещана быть интересней…
      • 0
        Про вользовательские загрузчики я обещал написать в другой статье. Про ворядок загрузки Вы уже второй просите написать, так что пожалуй добавлю.
        • 0
          вользовательские -> пользовательские.

          Пользуйтесь spell checker'ом, что ли…
    • +2
      Кстати, при загрузке никакой инициализации статиков не происходит — это уже потом, в процессе инициализации класса.
    • –1
      1) Это я писал.
      2) Добавлю в пост.
      3) Это уже другая тема ;)
  • +1
  • +3
    Автор, вы являетесь членом Санкт-Петербургской группы тестирования JVM? Если нет, то в статье явно не хватает ссылки на оригинал
  • +1
    Заключение просто порвало моск.
  • 0
    Context Classloader — загрузчик контекста текущего потока. Получить и установить данный загрузчик можно с помощью методов Thread.getContextClassLoader()/Thread.serContextClassLoader(). Загрузчик контекста устанавливается автоматически для каждого нового потока. При этом, используется загрузчик родительского потока.


    Исправьте ошибку:
    serContextClassLoader -> setContextClassLoader
  • –1
    Исправьте Выды на Виды
  • 0
    спасибо, интересная статья. я только не понял, почему после не обнаружения класса в кэше по всей иерархии загрузчиков, дальше попытка его найти начинается с конца иерархии? Логичнее было бы начинать с корня, т.е. с системного загрузчика.
    • 0
      Кажется, я вас не понял. Но попытаюсь пояснить. У каждого загрузчика свой кеш. Поиск осуществляется вверх по иерархии. Если поиск завершился неудачей, начинается загрузка. Загрузка — наоборот, осуществляется вниз по иерархии.
      • 0
        смотрите, поиск в кеше идет от Системного загрузчика к пользовательскому. Загрузка идет почему-то от пользовательского к системному — почему?
        • 0
          Все наоборот. Откуда вы это взяли?
          • 0
            ну вот же написано:

            Рассмотрим процесс загрузки более детально. Пусть в систем исполнения встретилась декларация переменной пользовательского класс Student.

            1) Системный загрузчик попытается поискать в кеше класс Student.
            _1.1) Если класс найден, загрузка окончена.
            _1.2) Если класс не найден, загрузка делегируется загрузчику расширений.
            2) Загрузчик расширений попытается поискать в кеше класс Student.
            _2.1) Если класс найден, загрузка окончена.
            _2.2) Если класс не найден, загрузка делегируется базовому загрузчику.
            3) Базовый загрузчик попытается поискать в кеше класс Student.
            _3.1) Если класс найден, загрузка окончена.
            _3.2) Если класс не найден, базовый загрузчик попытается его загрузить.
            __3.2.1) Если загрузка прошла успешно, она закончена ;)
            __3.2.2) Иначе управление предается загрузчику раширений.
            _3.3) Загрузчик расширений пытается загрузить класс.
            __3.3.1) Если загрузка прошла успешно, она закончена ;)
            __3.3.2) Иначе управление предается системному загрузчику.
            _3.4) Системный загрузчик пытается загрузить класс.
            __3.4.1) Если загрузка прошла успешно, она закончена ;)
            __3.4.2) Иначе генерируется исключение java.lang.ClassNotFoundException.
            • 0
              прошу прощения, я перепутал базовый и системный загрузчики. Названия у них уж больно похожие.
              • 0
                Рад, что вы разобрались.
              • 0
                Спасибо, все просто и понятно.
  • 0
    Вот еще интересная презентация. В основном в ней рассказывается про то, как реализована загрузка классов в OSGi, но и базовые моменты рассматриваются.

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