Java не загружает сразу все классы приложения при старте. Вместо этого классы подгружаются в память по мере необходимости:
- когда вы впервые обращаетесь к классу,
- либо явно через API загрузчиков классов (class loaders).
Зачем это нужно?
- Экономия памяти (загружаются только реально используемые классы).
- Возможность подгружать новые классы во время работы программы (плагины, hot reload).
- Разделение окружений (например, Tomcat изолирует приложения с помощью разных загрузчиков).
Когда JVM встречает новый класс, выполняются три шага:
-
Loading (загрузка) Класс-файл считывается загрузчиком (
ClassLoader) в байт-код. -
Linking (связывание)
- Verify — проверка корректности байткода.
- Prepare — выделение памяти под статические поля, установка значений по умолчанию.
- Resolve — связывание ссылок на другие классы.
-
Initialization (инициализация) Выполнение статических инициализаторов (
static { ... }) и присвоение реальных значений статическим полям.
В Java загрузчики образуют иерархию с делегированием: если класс не найден у родителя, ищет сам.
- Написан на C, встроен в JVM.
- Загружает классы стандартной библиотеки Java (
rt.jarилиjava.baseв Java 9+). - Это не объект класса
ClassLoader, а часть JVM.
- Загружает стандартные расширения Java (например, библиотеки из
$JAVA_HOME/lib/extв старых версиях или модули в Java 9+). - В Java 9 переименован в Platform ClassLoader.
- Загружает классы приложения из classpath (
-cpилиCLASSPATH). - Это обычный объект Java (
sun.misc.Launcher$AppClassLoaderили его аналог в новых версиях).
Вы можете написать свой загрузчик, чтобы:
- загружать классы из базы данных, сети, зашифрованных jar;
- реализовать модульность или горячую замену кода;
- изолировать классы разных приложений (например, в сервлет-контейнерах).
Пример:
class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassData(name); // читаем байты откуда угодно
return defineClass(name, bytes, 0, bytes.length);
}
}- При загрузке класса
ClassLoaderсначала спрашивает родителя, загружен ли класс. - Если родитель не нашёл, класс загружает сам.
- Это предотвращает подмену системных классов (
java.lang.String) пользовательскими.
Исключение: Некоторые загрузчики (например, в Tomcat, OSGi) частично нарушают эту модель, чтобы позволить загружать свои версии библиотек до системных.
JVM позволяет загружать классы явно:
Class<?> cls = Class.forName("com.example.MyClass");
Object obj = cls.getDeclaredConstructor().newInstance();или
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> cls = loader.loadClass("com.example.MyClass");Разница:
Class.forName()сразу инициализирует класс (выполняет static-блоки).loadClass()только загружает, без инициализации — до первого обращения.
- Сервлет-контейнеры (Tomcat, Jetty) — изолируют приложения.
- Spring / Hibernate — загружают классы через context class loader.
- OSGi / JPMS (Java 9+) — модульные системы.
- Плагины / скрипты — динамическое подключение новых модулей.
📌 Кратко для собеседования
Классы загружаются динамически по мере использования. За это отвечает иерархия загрузчиков: Bootstrap → Platform → Application. По умолчанию используется модель делегирования — сначала родитель, потом свой код. Классы можно загружать явно через
Class.forName()илиClassLoader.loadClass(), а при необходимости писать собственные загрузчики, например для плагинов или горячей подгрузки кода.