Класифікація та функції загрузчиков класів

Як відомо, Програми на Java транслюються в байт-код, що виконується віртуальною машиною Java (JVM) - програмою, обробній байтовий код і передавальної інструкції інтерпретатора. Зрозуміло, що перш ніж інтерпретувати байт-код, його необхідно завантажити в оперативну пам'ять комп'ютера. Отже, як же завантажується найперший клас?

Всі класи в Java завантажуються за допомогою загрузчиков класів. Спочатку роботи програми створюється 3 основних завантажувача класів:

базовий завантажувач (bootstrap)

завантажувач розширень (extention)

системний завантажувач (system / application)

Крім основних загрузчиков класів, існує можливість створення користувацьких загрузчиков класів. Про них ми поговоримо пізніше.

Завантажники класів є ієрархічними. Завантажувач, який завантажує основні системні класи, називається базовим (Bootstrap або Primordial) загрузчиком класів. Саме він завантажує внутрішні класи JDK і пакети java. * (Rt.jar і i18n.jar). Важливо зауважити, що базовий завантажувач є «Початковим або Кореневим» і частиною JVM, внаслідок чого його не можна створити всередині коду програми.

Тоді навіщо ж потрібні інші завантажувачі, якщо базовий завантажувач і так непогано виконує свою роботу? Навіщо знадобилося розбивати процедуру завантаження класів на кілька етапів? Щоб відповісти на це питання, потрібно розглянути інші завантажувачі класів і їх взаємодія.

Отже, завантажувач розширень - завантажує різні пакети розширень, які розташовуються в директорії / Lib / ext або іншій теці, описаної в системному параметрі java.ext.dirs. Це дозволяє оновлювати і додавати нові розширення без необхідності модифікувати настройки використовуваних додатків. Завантажувач розширень реалізований класом sun.misc.Launcher $ ExtClassLoader.

І, нарешті, системний завантажувач - завантажує класи, шляхи до яких вказані в змінній оточення CLASSPATH або шляху, які вказані в командному рядку після ключів -classpath або -cp. Системний завантажувач реалізований класом sun.misc.Launcher $ AppClassLoader.

Принцип роботи загрузчиков класів

Кожен завантажувач класів (крім Bootstrap) має батьківський завантажувач, і в більшості випадків він запитує батьківського завантажувача завантажити вказаний клас, перед тим як спробувати завантажити його самостійно.

Існує так само явний спосіб ініціювати завантаження необхідного класу. Явна ініціювання виконуватися за допомогою методів ClassLoader.loadClass () або Class.forName (). Наприклад явне ініціювання використовується при завантаженні JDBC драйверів: Class.forName ( "oracle.jdbc.driver.OracleDriver");

Ієрархія загрузчиков класів виглядає наступним чином:

public class ClassLoadersTest

public static void main (String [] args)

System. out. println (i. getClass (). getClassLoader ());

Виклик i.getClass (). GetClassLoader () поверне null, що свідчить про те, що клас був завантажений саме базовим загрузчиком.

Давайте розглянемо процес завантаження класів більш детально. Припустимо, у нас є якийсь клас, який ми будемо завантажувати (наприклад Integer). Процес завантаження буде наступним:

Системний завантажувач (sun.misc.Launcher $ AppClassLoader) перевірить, чи не завантажувався даний клас раніше. Якщо він вже завантажувався, то повертається даний клас з кешу. Якщо немає, системний завантажувач делегує пошук класу батьківського класу-завантажувачу.

Завантажувач розширень (sun.misc.Launcher $ ExtClassLoader) виконує таку ж процедуру

Нарешті, базовий завантажувач (bootstrap), завантажує клас Integer самостійно, оскільки у нього немає батьківського класу.

Таким чином, процес завантаження має одну важливу властивість, а саме делегування (рисунок 1). Це дозволяє завантажувати класи тим загрузчиком, який знаходиться найближче до базового в ієрархії делегування. Як наслідок пошук класів відбуватиметься в джерелах в порядку їх довіри: спочатку в бібліотеці core API, потім в папці розширень, потім в локальних файлах classpath.

Іншими словами, якби ви завантажили сторонню бібліотеку з класом Integer і вказали її в змінної шляху, то завантажився б все одно оригінальний Integer (ціле число).

Ще одна важлива властивість - кожен завантажувач має свій простір імен для створюваних класів. Тобто якщо класи однакові і знаходяться в одному пакеті, але завантажуються різними завантажувачами - вони вважаються різними. Таким чином, можна наприклад, створити два об'єкти Сінглтона, якщо постаратися :)

Призначені для користувача завантажувачі класів

В Java існує можливість створення власних загрузчиков класів. Це може бути корисно, коли немає можливості або небажано перераховувати всі використовувані бібліотеки при старті програми в CLASSPATH. Наприклад, в програмі має бути можливість динамічного завантаження плагінів. Або можливостей стандартного завантажувача недостатньо для завантаження потрібних класів.

Власні завантажувачі класів використовують всі сервери додатків і web-контейнери, що й зрозуміло - додатки, що розгортаються на сервері додатків, повинні завантажуватися динамічно, в іншому випадку перерахування в змінної CLASSPATH всіх бібліотек, які використовуються додатками, стає завданням нетривіальною.

За створення призначених для користувача загрузчиков класів відповідає клас ClassLoader. Для того, що б створити власний завантажувач класів, необхідно успадкувати від класу ClassLoader.

Процес завантаження класу більш детально

Процес завантаження класу складається з трьох частин:

Loading - на цій фазі відбувається пошук і фізична завантаження файлу класу в певному джерелі (в залежності від завантажувача). Цей процес визначає базове уявлення класу в пам'яті. На цьому етапі такі поняття як методи, поля і т.д. поки не відомі.

Linking - процес, який може бути розбитий на 3 частини:

Class preparation - на цьому етапі відбувається підготовки структури даних, що відображає поля, методи і реалізовані інтерфейси, які визначені в класі.

Resolving - дозвіл все класів, які посилаються на поточний клас.

Initialization - відбувається виконання статичних ініціалізаторів визначених у класі. Таким чином, статичні поля инициализируются стандартними значеннями.

винятки

При роботі загрузчиков класів найбільш часто зустрічаються такі виняткові ситуації:

1. ClassNotFoundException. кидається, коли програма намагається завантажитися клас по його назві (String) за допомогою таких засобів:

forName метод в класі Class.

findSystemClass метод в класі ClassLoader.

loadClass метод в класі ClassLoader.

Але класу з таким ім'ям не існує.

2. NoClassDefFoundError. кидається в таких випадках:

Коли архів, директорія, або інше джерело необхідних класів не додано до джерела поточного завантажувача класів або його предка.

Завантажувач-предок ні встановлено коректно.

Іноді проблеми, пов'язані із завантаженням класу, виявляються не тільки на етапі завантаження, але і на етапі використання класу.

вивантаження класів

У більшості випадків, життєвий цикл класу в віртуальній машині схожий з життєвим циклом об'єкта. JVM завантажує, пов'язує і ініціалізує класи, дозволяючи програмі користуватися ними, і вивантажує, коли в додатку вони більш не використовується. Важливо зауважити, що вивантаження класів не працює в тому випадку, якщо клас був завантажений Bootstrap загрузчиком.

Завантажені класи, незважаючи на те, що є повноцінними Java-об'єктами, зберігаються в особливій системної області пам'яті, званої permament generation (скорочено, PermGen) і керованої складальником сміття.

Вивантаження класів є важливою частиною механізму роботи JVM, оскільки Java програми можуть динамічно розширюватись під час роботи, завантажуючи призначені для користувача класи і, тим самим, займати багато місця в оперативній пам'яті. Тримати класи в пам'яті, які більше не будуть використовуватися, немає ніякого сенсу.

Конкретна політика вивантаження класів багато в чому залежить від реалізації віртуальної машини JVM.

висновок

Таким чином, ми з вами трохи наблизилися до розуміння процесу завантаження класів в JVM. Були розглянуті типи завантажувачів, їх ієрархія, фази завантаження класу і виняткові ситуації, які можуть виникнути в процесі.

Схожі статті