Как работает механизм загрузки классов (ClassLoader) в Java?

«Как работает механизм загрузки классов (ClassLoader) в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Загрузка классов в Java — это процесс поиска байт-кода класса (файла .class) и создания объекта java.lang.Class. Этим управляет иерархия ClassLoader'ов, работающих по модели делегирования (parent-delegation).

Иерархия встроенных загрузчиков:

  1. Bootstrap ClassLoader (null): Написан на нативном коде. Загружает основные классы Java Core (java.lang.*, java.util.* и др.) из rt.jar и других системных библиотек.
  2. Platform (Extension) ClassLoader: Загружает классы из каталогов расширений (jre/lib/ext или указанных через java.ext.dirs).
  3. Application (System) ClassLoader: Загружает классы из classpath (параметр -cp, переменная CLASSPATH).

Модель делегирования «снизу вверх»: Когда Application ClassLoader получает запрос на загрузку класса:

  1. Он не ищет класс сразу. Сначала он делегирует запрос своему родителю — Platform ClassLoader.
  2. Platform ClassLoader, в свою очередь, делегирует запрос своему родителю — Bootstrap ClassLoader.
  3. Если Bootstrap не может найти класс (не в его области), управление возвращается Platform, который пытается загрузить класс сам.
  4. Если и Platform не может, запрос возвращается Application, который ищет класс в classpath.

Пример кода:

// Явная загрузка класса
Class<?> clazz = Class.forName("com.example.MyClass");
// Получение загрузчика класса
ClassLoader loader = MyClass.class.getClassLoader();
System.out.println(loader); // sun.misc.Launcher$AppClassLoader
System.out.println(loader.getParent()); // sun.misc.Launcher$ExtClassLoader
System.out.println(loader.getParent().getParent()); // null (Bootstrap)

Зачем нужна модель делегирования?

  • Безопасность: Не позволяет пользовательскому классу заменить системный (например, свой java.lang.String).
  • Уникальность: Класс загружается единожды своим «родительским» загрузчиком, что предотвращает конфликты.
  • Иерархия видимости: Классы, загруженные родительским загрузчиком, видны дочерним, но не наоборот.

Важные исключения:

  • ClassNotFoundException: Загрузчик не смог найти байт-код класса.
  • NoClassDefFoundError: Класс был найден при компиляции, но не доступен во время выполнения (например, ошибка в статическом инициализаторе).