Санкт-Петербургская группа тестирования JVM


« Переделан java.sun.c... | Main | Java по-русски »
20060703 понедельник Июль 03, 2006

Основы динамической загрузки классов в Java

Основа работы с классами в Java—загрузчики, обычные Java-объекты, предоставляющие интерфейс для поиска и создания объекта класса по его имени во время работы приложения.

Динамическая загрузка классов в Java имеет ряд особенностей:

Модель динамической загрузки

Во время работы приложения, не только система, но и пользователь (расширяя функциональность класса java.lang.ClassLoader) имеет возможность создавать загрузчики классов. Связь между различными загрузчиками регламентируется моделью делегирования загрузки.

Модель делегирования загрузки представляет собой дерево, описывающее связь «родитель-потомок» между представленными загрузчиками. Каждый загрузчик, за исключением базового, должен иметь родительский загрузчик, причем единственный. Дочерний загрузчик может направить (делегировать) запрос на создание класса своему родительскому загрузчику. Для пользовательского загрузчика родительским по-умолчанию является системный, но ничто не мешает при создании явно указаывать любой другой доступный загручик.

Во время начала работы автоматически создается 3 различных загрузчика классов, отвечающих за загрузку различных компонент системы:

Основные системные классы, которые обычно находятся в jar-файлах в директории jre/lib загружаются именно базовым загрузчиком. Опция -Xbootclasspath предоставляет возможность модифицировать набор классов, доступных для загрузки базовым загрузчиком.

Ещё одной особенностью базового загрузчика является тот факт, что он не может быть создан из Java-кода. Зачастую, это обуславливается тем, что его реализация является частью виртуальной машины.

Работа с базовым загрузчиком напрямую невозможна. Например, вызов java.lang.Object.class.getClassLoader() вернет null, что свидетельствует о том, что класс был загружен именно базовым загрузчиком.

Загрузчик расширений является дочерним для базового загрузчика. Его основная задача – загрузка различных пакетов расширений, которые обычно помещаются в jre/lib/ext. Это позволяет обновлять и добавлять новые расширения без необходимости модифицировать настройки используемых приложений.

Системный загрузчик ответственнен за загрузку классов из директорий, перечисленных в переменной окружения CLASSPATH. Данный загрузчик можно получить вызвав метод ClassLoader.getSystemClassLoader().

Во время загрузки, поиск запрошенного класса производится в следующей последовательности:

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

Как следствие, все системные классы загружаются базовым загрузчиком (например, java.lang.Object, java.lang.String и т.д.). Более того, ряд системных классов, по соображениям безопасности, могут быть загружены только лишь базовым загрузчиком - любые попытки создать один из таких классов другим загрузчиком завершатся неудачно.

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

С учетом правил делегирования, класс может быть создан загрузчиком, отличным от того: который инициировал его загрузку. Поэтому для каждого конкретного класса особое значение имеют два загрузчика—инициировавший загрузку (initiating loader) и непосредственно создавший его (defining loader).

Если все загрузчики корректно реализуют модель делегирования, с учетом правила «сначала предлагать загрузить класс родителю, а потом пытаться сделать это самому», базовый загрузчик будет первым кто попытается найти и создать класс, а потом данную операцию последовательно вниз по пути делегирования будут пытаться осуществить остальные загрузчики вплоть до инициировавшего загрузку. Это позволяет создавать класс тем загрузчиком, который наиболее близко находится к базовому, тем самым максимизируя область видимости класса.

Процесс создания класса

После того, как байт-код для запрошенного класса был найден, необходимо на его основе создать класс - получить полноценный объект, представляющий Java-класс. Для этого используется метод ClassLoader.defineClass().

Процесс конструирования класса состоит из ряда последовательных фаз:

По завершению всех перечисленных фаз, класс полностью готов к использованию.

Если виртуальной машине передать параметр -verbose:class, то информация о каждой успешной загрузке класса будет выводиться на консоль.

Например, запустив java -verbose:class -help получим:

[Opened /opt/sun-jdk-1.5.0.06/jre/lib/rt.jar]

... список открываемых jar-файлов...

[Opened /opt/sun-jdk-1.5.0.06/jre/lib/charsets.jar]

[Loaded java.lang.Object from /opt/sun-jdk-1.5.0.06/jre/lib/rt.jar]

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

[Loaded java.lang.SystemClassLoaderAction from /opt/sun-jdk-1.5.0.06/jre/lib/rt.jar]

Usage: java [-options] class [args...]

... информация о допустимых параметрах ...

[Loaded java.lang.Shutdown from /opt/sun-jdk-1.5.0.06/jre/lib/rt.jar]

[Loaded java.lang.Shutdown$Lock from /opt/sun-jdk-1.5.0.06/jre/lib/rt.jar]

Явное и неявное инициирование загрузки класса

Существует несколько способов инициировать загрузку требуемого класса:

В случае, если вызывается метод Class.forName(), по умолчанию используется загрузчик, создавший текущий класс. Хотя есть возможность и явно указать желаемый загрузчик.

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

Выгрузка ранее загруженных классов

В общем случае, класс может быть выгружен только тогда, когда в приложении он более не используется. Конкретная же политика выгрузки классов во многом зависит от реализации виртуальной машины и в дальнейшем будет описываться поведение Sun HotSpot JVM.

Загруженные классы, несмотря на то, что являются полноценными Java-объектами, хранятся в особой системной области памяти, называемой permament generation (сокращенно, PermGen) и управляемой сборщиком мусора. Соответственно, класс будет выгружен только тогда, когда на него не осталось ссылок. В случае, если класс был создан пользовательским загрузчиком, прямая ссылка на него храниться загрузчиком и класс может быть выгружен только после успешной выгрузки загрузчика. Это правило распространяется и на загрузчики, управляемые системой. Соответственно, классы, созданные базовым загрузчиком, не могут быть выгружены в принципе. На практике это верно и для системного загрузчика с загрузчиком расширений—их выгрузка во время работы приложения не предусмотрена.

Параметр -verbose:class также позволяет получить информацию и о выгружаемых классах.

Владимир Иванов

опубликовал vmrobot ( июл 03 2006, 10:16:20 PM MSD ) Permalink Комментарии [10]

Trackback URL: http://blogs.sun.com/vmrobot/entry/%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D1%8B_%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9_%D0%B7%D0%B0%D0%B3%D1%80%D1%83%D0%B7%D0%BA%D0%B8_%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%B2_%D0%B2
Комментарии:

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

Интересно, откуда была взята информация о системном загрузчике? Мне всегда казалось, что он должен вести себя точно также как и любые другие пользовательские загрузчики.

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

опубликовал dshe Июль 27, 2006 at 04:36 PM MSD #

Большое спасибо за замечание. Изначально я почерпнул информацию о специфическом поведении системного загрузчика при выгрузке классов из обсуждений с разработчиками виртуальной машины. Как оказалось, после исследования исходного кода и проведения ряда экспериментов, поведение системных загрузчиков при выгрузке классов ничем не отличается от пользовательских. Точнее, выгрузка классов, загруженных системными загрузчиками не производится. Так что прошу прощения за неумышленную дезинформацию =) В свою очередь хотел бы поинтересоваться каким образом вызов статического инициализатора при повторной загрузке класса может нарушить целостность системы?

опубликовал Владимир Иванов Август 07, 2006 at 02:21 PM MSD #

Перезагрузка класса (которая похоже была до 1.2) приводит к тому, что статические поля теряют свою информацию. Фактически это означает, что такой безобидный код

MyClass.staticVar = 10;
System.out.println(MyClass.staticVar);

может вывести не 10, а, скажем, 0. Просто потому, что между записью в статическое поле и его чтением, могла бы произойти сборка, которая сколлектила MyClass.

Сейчас, согласно JVMS:

2.17.8 Unloading of Classes and Interfaces

A class or interface may be unloaded if and only if its class loader is unreachable. The bootstrap class loader is always reachable; as a result, system classes may never be unloaded.

Это достигается тем, что класслоадер содержит ссылки на все свои загруженные классы (можно посмотреть на исходники java.lang.ClassLoader -- там об этом говорится явно), а класс -- на свой класслоадер.

Важно отметить, что для system класслоадера здесь не делается исключение. И в принципе, классы, загруженные system класслоадером (как и любым другим класслоадером, кроме bootstrap), могут быть выгружены. Однако в жизни этого достичь сложно поскольку ссылка на system класслоадер, как минимум, хранится в статическом поле системного класса (т.е. загруженного bootstrap класслоадером): java.lang.ClassLoader.scl

Вот некоторые ссылки по данной теме:

System classes lose static state on class unloading.

Clarifications and Amendments to the JLS

опубликовал dshe Август 08, 2006 at 11:19 AM MSD #

Огромное спасибо за исчерпывающий ответ =)

опубликовал Владимир Иванов Август 08, 2006 at 01:35 PM MSD #

Добрый день! Возник вопрос, не могли бы поподробней рассказать как import-ы обрабатываются? 1. Классы, указанные в import-ах какой загружает обычно загружает? 2. Он их сразу загружает, при выполнении этих строк кода, или тут тоже имеет место упомянутая lazy-load, т.к. они загружается только в той строке, где они реально используются? Вообще было неплохо дополнить статью вот таким описанием работы import-а, а то как-то все в некотором роде абстрактно: загрузчики, классы, объекты... было бы интересно почитать еще и про то вот что например происходит в строке с import, затем в сроке первого использования этого класса, указанного прежде в import и т.д. Заранее спасибо, в остальном статья очень понравилась.

опубликовал docker Сентябрь 18, 2006 at 12:45 AM MSD #

Насколько я знаю, импорты никак не обрабатываются на моменте исполнения. Директива import служит только в момент компиляции текста программы. В байт-коде присутствуют только full-qualified class names.

опубликовал null Август 09, 2007 at 01:08 PM MSD #

Привет! Не подскажете, чем отличается обычная загрузка класса от явной Class.forName()? Не может ли использование явной загрузки привести к тому, что память permament generation исчерпается? Насколько я вижу по исходному коду - Class.forName просто вызывает нативный метод. Если, например, постоянно вызывать этот метод для одного и того же имени не приведет ли это к OutOfMemory: PermGen Space?

опубликовал nata Сентябрь 21, 2007 at 08:51 PM MSD #

Насколько я понял, под "обычной" подразумевается вызов метода ClassLoader.loadClass(...). Отличие Class.forName() в том, что в зависимости от конкретного объекта Class, загрузка может быть инициирована различными class loader'ами. В первом случае, загрузка будет инициироваться загрузчиком, метод которого вызывается.

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

опубликовал Владимир Иванов Сентябрь 22, 2007 at 01:14 AM MSD #

В вашем материале я нашел несколько неточностей (кхе, кхе)
во первых формулировка
----
Процесс конструирования класса состоит из ряда последовательных фаз:
.... много букав .....
Инициализация

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

опубликовал black Январь 13, 2008 at 11:40 PM MSK #

black: Спасибо большое за конструктивные замечания.

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

По поводу "обычных полей" - фраза "присваивание полям класса значений по-умолчанию" подразумевает инициализацию статических полей, и не несет никакой информации об инициализации объектов загруженного типа. Каюсь, неясно выразился =)

опубликовал Владимир Иванов Февраль 03, 2008 at 09:03 PM MSK #

Опубликовать комментарий:

Имя
E-Mail:
URL:

Ваш комментарий:

HTML Syntax: Отключен

Хиты страниц за сегодня: 2