Завантажувач класів
Використовується для постачання в JVM скомпілюваного байт-коду, який, як правило, зберігається у файлух з розширенням.class
, але може бути також отриманий з інших джерел, наприклад, завантажений по мережі або згенерований самим додатком. Відповідно до специфікації Java SE для того, щоб отримати код, що працює в JVM, необхідно виконати три етапи:
-
завантаження байт-коду з ресурсів та створення екземпляра класу
Class
сюди входить пошук запитаного класу серед завантажених раніше, отримання байт-коду для завантаження та перевірка його коректності, створення екземпляра класу
Class
(для роботи з ним в runtime), завантаження батьківських класів. Якщо батьківські класи та інтерфейси були завантажені, те й аналізований клас вважається не завантаженим. -
зв'язування (або лінківка)
за специфікацією цей етап розбивається ще на три стадії:
- Verification відбувається перевірка коректності отриманого байт-коду.
- Preparation , Виділення оперативної пам'яті під статичні поля та ініціалізація їх значеннями за умовчанням (при цьому явна ініціалізація, якщо вона є, відбувається вже на етапі ініціалізації).
- Resolution , дозвіл символьних посилань типів, полів та методів.
-
ініціалізація отриманого об'єкта
тут, на відміну від попередніх пунктів, начебто все зрозуміло, що має відбуватися. Було б, звичайно, цікаво розібратися, як саме це відбувається.
- Клас повинен бути повністю завантажений перш, ніж слинкований.
- Клас повинен бути повністю перевірений та підготовлений перш ніж проініціалізований.
- Помилки дозволу посилань відбуваються під час виконання програми, навіть якщо були виявлені на етапі лінківки.
Типи завантажувачів Java
У Java існує три стандартні завантажувачі, кожен з яких здійснює завантаження класу з певного місця:-
Bootstrap – базовий завантажувач, також називається Primordial ClassLoader.
завантажує стандартні класи JDK із архіву rt.jar
-
Extension ClassLoader – завантажувач розширень.
завантажує класи розширень, які за умовчанням перебувають у каталозі jre/lib/ext, але можуть бути задані системною властивістю java.ext.dirs
-
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 Class<?> loadClass(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, для користувача це може бути щось своє. Таким чином, хід завантаження класів йде у зворотному напрямку – від кореневого завантажувача до поточного. Коли байт-код класу знайдено,Class
. Як неважко помітити, описана схема завантаження схожа на наведену реалізацію методу loadClass(String name)
. Нижче можна розглянути цю схему на діаграмі.
Як висновок
На перших кроках вивчення мови немає якоїсь особливої необхідності у розумінні того, як відбувається завантаження класів у Java, але знання цих базових принципів дозволить не впадати у відчай, зустрівши такі помилки, якClassNotFoundException
або NoClassDefFoundError
. Ну чи хоча б приблизно розуміти, у чому корінь проблеми. Так виняток ClassNotFoundException
виникає при динамічному завантаженні класу під час виконання програми, коли завантажувачі не можуть знайти необхідний клас ні в кеші, ні шляхом знаходження класів. А ось помилкаNoClassDefFoundError
є критичнішою і виникає в тому випадку, коли під час компіляції шуканий клас був доступний, але не видно під час виконання програми. Це може статися, якщо в поставку програми забули увімкнути бібліотеку, яку вона використовує. Ну і сам факт розуміння принципів пристрою того інструменту, яким користуєшся в роботі (не обов'язково чітке і детальне занурення в його надра), додає деяку ясність у розумінні процесів, що протікають всередині цього механізму, що, у свою чергу, веде до впевненого використання цього інструменту.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ