В этой статье вы узнаете, как JVM загружает классы по мере необходимости, какие бывают загрузчики, почему нельзя подменить String, и откуда берётся ClassNotFoundException. Полезно для junior специалиста.
Когда вы запускаете Java-приложение, JVM не грузит все классы сразу. Она делает это по мере необходимости и именно ClassLoader-ы отвечают за загрузку байткода в память. Давайте разберёмся, как это работает и почему важно понимать эту механику.
Кто такие ClassLoader-ы?
ClassLoader – это часть JVM, которая находит .class-файлы, загружает их в память и создаёт объект Class, который уже можно использовать в программе.
В Java есть три основных загрузчика (можете создать свой):
Bootstrap ClassLoader – загружает ядро Java: java.lang.*, включая String, Object и др.
Platform ClassLoader (в старых версиях Extension) – отвечает за платформенные модули (например, javax.*).
Application ClassLoader – загружает ваши классы, которые находятся в classpath.
Практический пример:
public class Main {
public static void main(String[] args) {
MyClass.myMethod();
}
}public class MyClass {
public static void myMethod() {
System.out.println("Метод myMethod() вызван!");
}
}Давайте разберем первый этап работы JVM – загрузку классов. У нас есть простая программа с классом Main, который вызывает метод из класса MyClass.
Что происходит при запуске:
JVM читает наш код и понимает, что для выполнения программы нужны два класса:
· Main (основной класс с методом main)
· MyClass (класс, который используется в программе)
JVM видит строку MyClass.myMethod() и понимает, что нужно найти класс MyClass
JVM запускает процесс Class Loading для загрузки необходимых классов в память
Почему JVM не грузит все классы сразу? Потому что это было бы неэффективно: даже неиспользуемые классы заняли бы память и замедлили запуск. Вместо этого Java применяет ленивую загрузку (lazy class loading) — класс загружается только тогда, когда к нему впервые обращаются. То есть если в нашей программе никогда не происходит обращения к классу MyClass, то JVM его не загрузит — даже если файл MyClass.class физически присутствует в classpath.
Иерархия загрузчиков

Теперь давайте посмотрим на механизм делегирования в ClassLoader-ах. JVM использует иерархическую систему загрузки классов. Каждый загрузчик классов будет искать в своей зоне ответственности. При этом каждый ClassLoader сначала делегирует загрузку своему родителю, и только если родитель не может найти класс, сам начинает поиск.
Последовательность поиска класса MyClass:
Application ClassLoader получает запрос на загрузку MyClass
Он не ищет сразу в своей области – сначала делегирует запрос Platform ClassLoader
Platform ClassLoader тоже не знает, где MyClass – делегирует запрос Bootstrap ClassLoader.
Bootstrap ClassLoader ищет в системных библиотеках – там MyClass нет, тогда возвращает null
Запрос возвращается обратно из Platform в Application
Только теперь Application ClassLoader ищет MyClass в classpath – и находит его!
Для чего нужен такой мудреный механизм поиска? Главная причина – безопасность. Представьте, что создали свой класс java.lang.String с методом stealPassword().
package java.lang;
public class String {
public static void stealPassword() {
System.out.println("Пароль украден!");
}
}Без делегирования JVM могла бы загрузить его вместо оригинального String. Но благодаря иерархии Bootstrap ClassLoader всегда загружает java.lang. первым – и никто не может его подменить.
А когда возникает ClassNotFoundException?
Если ни один из загрузчиков не смог найти нужный класс, возникает ошибка ClassNotFoundException. По сути, это означает, что ClassLoader не обнаружил соответствующий .class-файл в своей области поиска.
Заключение
В этой статье мы разобрали, как JVM загружает классы: от ленивой загрузки и иерархии ClassLoader-ов до механизма делегирования и защиты критически важных классов вроде java.lang.String. Теперь вы не только понимаете, почему возникает ClassNotFoundException, но и знаете, что происходит «под капотом» уже при запуске простейшей программы.
Это моя первая статья и я очень надеюсь, что она оказалась для вас полезной.