Получить доступ

Загрузка классов и class loaders

Жизненный цикл класса (loading/linking/initialization), иерархия загрузчиков с делегированием, триггеры инициализации и изоляция.

Жизненный цикл класса

Класс проходит три фазы. Loading — class loader находит и читает байткод .class, создаёт объект Class в metaspace. Linking делится на verification (проверка корректности и безопасности байткода), preparation (выделение статических полей со значениями по умолчанию) и resolution (разрешение символьных ссылок). Initialization — выполнение статических инициализаторов и присваивание статических полей.

Разделение важно: на preparation static int x уже существует со значением 0, а реальное значение из объявления присваивается только на initialization.

Иерархия загрузчиков и делегирование

Загрузчики образуют цепочку: bootstrap (ядро JDK, java.base), platform и application (classpath приложения). По умолчанию работает parent-first delegation: загрузчик сначала просит родителя загрузить класс и берётся за дело сам, только если тот не смог.

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

Иерархия загрузчиков классов Bootstrap → Platform → Application: запрос делегируется вверх к родителю, и только если тот не нашёл класс, грузит сам.
Parent-first делегирование: загрузчик сначала спрашивает родителя и грузит класс сам лишь как запасной вариант.

Когда класс инициализируется

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

Инициализация потокобезопасна: JVM гарантирует, что статический инициализатор выполнится один раз даже при гонке потоков — на этом основан, например, идиом «holder» для ленивого синглтона.

class Holder { static final Config C = load(); } // инициализируется
// лениво и потокобезопасно при первом обращении к Holder.C

Идентичность класса и изоляция

Класс в рантайме идентифицируется парой (полное имя + загрузчик). Один и тот же .class, загруженный двумя разными загрузчиками, — это два разных типа, и присваивание между ними даст ClassCastException.

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

Типичные проблемы

ClassNotFoundException — класс не найден при динамической загрузке (рефлексия), это checked-исключение. NoClassDefFoundError — класс был при компиляции, но отсутствует/не загрузился в рантайме (или упала его инициализация ранее) — это Error.

Различать их полезно: первое почти всегда про classpath/имя, второе часто про сломанную инициализацию или несовпадение версий.

Как отвечать на собеседовании

Разложите loading/linking(verify/prepare/resolve)/initialization, объясните parent-first delegation и зачем оно (безопасность системных классов), назовите триггеры ленивой инициализации и её потокобезопасность (holder-синглтон), и различите ClassNotFoundException и NoClassDefFoundError.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем