← Назад к вопросам

Как работает Java Classloader?

2.8 Senior🔥 191 комментариев
#JVM и управление памятью#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Java Classloader: Как это работает

Classloader это механизм, который загружает классы в память JVM во время выполнения. Без него Java не могла бы динамически загружать и запускать классы.

Иерархия Classloaders

В Java существует 3 типа classloaders, организованные в иерархию:

Bootstrap Classloader (встроенный, родитель для всех)
        ↓
Extension Classloader (загружает классы из ext/)
        ↓
Application Classloader (загружает из classpath)
        ↓
Custom Classloader (если нужно)

1. Bootstrap Classloader

Это встроенный классозагрузчик, который загружает основные классы JDK.

public class ClassLoaderExample {
    public static void main(String[] args) {
        // Bootstrap загружает классы из rt.jar, jce.jar
        String[] coreClasses = {
            "java.lang.String",
            "java.lang.Object",
            "java.util.ArrayList",
            "java.io.File"
        };
        
        for (String className : coreClasses) {
            try {
                Class clazz = Class.forName(className);
                ClassLoader loader = clazz.getClassLoader();
                System.out.println(className + " -> " + loader);
                // Выведет: null (null означает Bootstrap Classloader)
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}
// Вывод:
// java.lang.String -> null (Bootstrap)
// java.lang.Object -> null (Bootstrap)
// java.util.ArrayList -> null (Bootstrap)

2. Extension Classloader

Загружает классы из каталога расширений JDK.

public class ExtensionClassLoaderExample {
    public static void main(String[] args) {
        // Extension Classloader загружает из $JAVA_HOME/lib/ext/
        ClassLoader extLoader = ClassLoader.getSystemClassLoader().getParent();
        System.out.println("Extension Classloader: " + extLoader);
        System.out.println("Родитель: " + extLoader.getParent());
    }
}
// Вывод:
// Extension Classloader: sun.misc.Launcher$ExtClassLoader@...
// Родитель: null (родитель это Bootstrap)

3. Application Classloader

Таже запускает ваше приложение, загружает классы из classpath.

public class ApplicationClassLoaderExample {
    public static void main(String[] args) {
        // Application Classloader загружает классы из classpath
        ClassLoader appLoader = ClassLoader.getSystemClassLoader();
        System.out.println("Application Classloader: " + appLoader);
        
        // Все наши классы загружаются этим classloader
        ClassLoader myClassLoader = ApplicationClassLoaderExample.class.getClassLoader();
        System.out.println("Мой классозагрузчик: " + myClassLoader);
        System.out.println("Одинаковые? " + (appLoader == myClassLoader));
    }
}
// Вывод:
// Application Classloader: sun.misc.Launcher$AppClassLoader@...
// Мой классозагрузчик: sun.misc.Launcher$AppClassLoader@...
// Одинаковые? true

Процесс загрузки класса

1. Loading (Загрузка)

Classloader читает .class файл и создаёт бинарное представление в памяти.

public class ClassLoadingExample {
    public static void main(String[] args) throws ClassNotFoundException {
        // Loading: classloader ищет MyClass.class
        // 1. Ищет в Bootstrap (не найдёт)
        // 2. Ищет в Extension (не найдёт)
        // 3. Ищет в Application classpath (НАЙДЁТ)
        
        ClassLoader loader = MyClass.class.getClassLoader();
        System.out.println("MyClass загружена с помощью: " + loader);
        
        // Динамическая загрузка
        String className = "com.example.MyClass";
        Class clazz = Class.forName(className);
        System.out.println("Класс загружен: " + clazz.getName());
    }
}

2. Linking (Связывание)

Верификация, подготовка и резолюция символов.

// Verification: проверяется корректность bytecode
// Preparation: выделяется память для static полей
// Resolution: символические ссылки заменяются прямыми

public class LinkingExample {
    public static int counter = 0;  // Preparation: выделяется память здесь
    public static final int MAX = 100;  // Optimization
    
    public static void main(String[] args) {
        // Resolution: вызов метода другого класса разрешается
        String result = OtherClass.staticMethod();
    }
}

3. Initialization (Инициализация)

Выполняются static блоки и инициализаторы.

public class InitializationExample {
    static {
        System.out.println("Статический блок инициализации");
    }
    
    static int value = initValue();
    
    static int initValue() {
        System.out.println("Инициализация static поля");
        return 42;
    }
    
    public static void main(String[] args) {
        System.out.println("Main method");
        System.out.println("Value: " + value);
    }
}
// Вывод:
// Статический блок инициализации
// Инициализация static поля
// Main method
// Value: 42

Parent Delegation Model (Модель делегирования родителю)

Каждый classloader при запросе загрузить класс сначала спрашивает у родителя.

public class ParentDelegationExample {
    public static void main(String[] args) throws ClassNotFoundException {
        // Когда Application Classloader просят загрузить класс:
        // 1. Спрашивает Extension Classloader
        // 2. Extension спрашивает Bootstrap
        // 3. Bootstrap ищет в rt.jar
        // 4. Если не найдёт, Extension ищет в ext/
        // 5. Если не найдёт, Application ищет в classpath
        // 6. Если не найдёт - ClassNotFoundException
        
        try {
            Class.forName("java.lang.String");
            // Загружена Bootstrap (в памяти всегда одна копия)
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Custom Classloader

Можно создать собственный classloader для специальных случаев.

// Собственный classloader
public class CustomClassLoader extends ClassLoader {
    private String classPath;
    
    public CustomClassLoader(String classPath) {
        super(ClassLoader.getSystemClassLoader());
        this.classPath = classPath;
    }
    
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        // name = "com.example.MyClass"
        // Преобразуем в путь: com/example/MyClass.class
        String path = name.replace(".", "/") + ".class";
        String fullPath = classPath + "/" + path;
        
        try {
            byte[] bytes = readClassBytes(fullPath);
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    
    private byte[] readClassBytes(String path) throws IOException {
        // Читать файл и вернуть byte array
        java.nio.file.Path filePath = java.nio.file.Paths.get(path);
        return java.nio.file.Files.readAllBytes(filePath);
    }
}

// Использование
public class CustomLoaderExample {
    public static void main(String[] args) throws ClassNotFoundException {
        CustomClassLoader loader = new CustomClassLoader("/custom/classes");
        Class<?> myClass = loader.loadClass("com.example.MyClass");
        System.out.println("Загружена через: " + myClass.getClassLoader());
    }
}

Проблемы и решения

ClassNotFoundException vs NoClassDefFoundError

public class ExceptionExample {
    public static void main(String[] args) {
        // ClassNotFoundException: динамическая загрузка
        try {
            Class.forName("com.nonexistent.Class");
        } catch (ClassNotFoundException e) {
            System.out.println("Класс не найден при динамической загрузке");
        }
        
        // NoClassDefFoundError: статическая ссылка
        // MyMissingClass myObj = new MyMissingClass();
        // Вызовет NoClassDefFoundError если класс был доступен при компиляции,
        // но недоступен при выполнении
    }
}

ClassLoader Leak (утечка classloader)

// Проблема: классы держаттся в памяти дольше чем нужно
public class ClassLoaderLeakExample {
    private static List<Class<?>> loadedClasses = new ArrayList<>();
    
    public void loadClass(String className) throws ClassNotFoundException {
        Class<?> clazz = Class.forName(className);
        loadedClasses.add(clazz);  // УТЕЧКА: класс удержится в памяти
    }
}

// Решение: использовать WeakHashMap или URLClassLoader.close()
public class ClassLoaderProperExample implements Closeable {
    private URLClassLoader customLoader;
    
    public ClassLoaderProperExample(URL[] urls) {
        customLoader = new URLClassLoader(urls, 
            ClassLoader.getSystemClassLoader());
    }
    
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return customLoader.loadClass(name);
    }
    
    @Override
    public void close() throws IOException {
        customLoader.close();  // Освобождаем ресурсы
    }
}

Отладка Classloader

# Логирование загрузки классов
java -verbose:class MyApplication
# Выведет все загружаемые классы

# Трассировка classloader
java -XX:+TraceClassLoading MyApplication

# Проверить classpath
java -XshowSettings:properties -version

# Найти где загружен класс
public void findClass(String className) throws ClassNotFoundException {
    Class<?> clazz = Class.forName(className);
    URL location = clazz.getProtectionDomain().getCodeSource().getLocation();
    System.out.println("Класс находится: " + location);
}

Практическое применение

public class ClassLoaderUsageExample {
    public static void main(String[] args) throws Exception {
        // 1. Динамическая загрузка плагинов
        URL[] urls = {new File("plugins/").toURI().toURL()};
        URLClassLoader loader = new URLClassLoader(urls);
        Class<?> pluginClass = loader.loadClass("com.plugin.MyPlugin");
        
        // 2. Переиспользование классов в разных версиях
        // (используется в контейнерах типа Docker, Kubernetes)
        
        // 3. Изоляция классов в многотенантных приложениях
        // (каждый тенант имеет свой classloader)
    }
}

Ключевые пункты

  1. Classloader загружает классы в память при запросе
  2. Три уровня: Bootstrap -> Extension -> Application
  3. Parent Delegation Model: ребёнок сначала спрашивает родителя
  4. Loading -> Linking -> Initialization
  5. Custom classloaders для специальных случаев
  6. Помни о утечках памяти и правильно закрывай ресурсы
  7. Class vs Object: Class это метаинформация, Object это экземпляр

Понимание Classloader критично для отладки проблем с импортом классов, версионированием зависимостей и многотенантных приложений.