Данная статья впервые опубликована на личном сайте, при необходимости перепечатки просьба указывать источник:Модель родительского делегирования в загрузчике классов, этого достаточно
В предыдущей статье мы прочесалиОсновные концепции загрузчиков классов: жизненный цикл классов, роль загрузчиков классов, время загрузки и выгрузки классов и т. д. В этой статье мы продолжаем обзор знаний о загрузчиках классов из предыдущей статьи, в основном включая: Какие загрузчики классов есть в JVM? Каковы отношения между ними? Каков механизм родительского делегирования?
Модель родительского делегирования
Погрузчики четырех классов
С точки зрения JVM существует два основных типа загрузчиков классов: Bootstrap ClassLoader и другие загрузчики классов Bootstrap ClassLoader реализованы на языке C++ и являются частью самой виртуальной машины, остальные загрузчики классов реализованы на языке Java и не принадлежат к виртуальным машинам.Все они наследуются от абстрактного класса java.lang.ClassLoader.
С точки зрения разработчика Java необходимо понимать модель родительского делегирования загрузчика классов, как показано на следующем рисунке:
-
Bootstrap ClassLoader: загрузчик классов Bootstrap, этот загрузчик классов будет отвечать за сохранение в каталоге
/lib по пути, указанному параметром -Xbootclasspath, и загрузку библиотеки классов jar, распознаваемой виртуальной машиной, в память. Грубо говоря, те классы, которые мы обычно используем в начале java.lang, должны быть загружены Bootstrap ClassLoader. -
Extension ClassLoader: загрузчик класса расширения, реализованный sun.misc.Launcher$ExtClassLoader, отвечает за загрузку файлов в каталог
/lib/ext или по пути, указанному системной переменной java.ext.dirs Все библиотеки классов. -
Application ClassLoader: загрузчик классов приложений, реализуемый sun.misc.Launcher$AppClassLoader, отвечает за загрузку всех библиотек классов по пути, указанному пользовательской переменной среды CLASSPATH. Если приложение не настроило свой собственный загрузчик классов, это загрузчик классов по умолчанию в программе Java.
-
Определяемый пользователем загрузчик классов: при необходимости пользователи могут реализовать свой собственный загрузчик классов.Вообще говоря, пользовательский загрузчик классов требуется в следующих ситуациях: (1) Загрузка классов изолированно. Чтобы реализовать изоляцию промежуточного программного обеспечения и модулей приложений, некоторые платформы требуют, чтобы промежуточное программное обеспечение и приложения использовали разные загрузчики классов; (2) изменить способ загрузки классов. Родительская модель делегирования загрузки классов не является обязательной, и пользователи могут динамически загружать классы в определенный момент времени в соответствии со своими потребностями; (3) расширить источники загрузки классов, такие как загрузка классов из баз данных и сетей; (4) предотвратить источник утечка кода. Код Java легко декомпилировать и подделывать.Чтобы предотвратить утечку исходного кода, вы можете зашифровать файл байт-кода класса и написать собственный загрузчик классов для загрузки ваших собственных классов приложений.
Пример 1: Различные загрузчики классов
В приведенном ниже коде java.util.HashMap — это класс в пакете rt.jar, поэтому его загрузчик классов имеет значение null, а класс DNSNameService — это класс в пакете jar, помещенный в каталог ext, поэтому его загрузчик классов ExtClassLoader; загрузчик классов MyClassLoaderTest является загрузчиком классов приложения.
import java.util.HashMap;
import sun.net.spi.nameservice.dns.DNSNameService;
public class MyClassLoaderTest {
public static void main(String[] args) {
System.out.println("class loader for HashMap: " + HashMap.class.getClassLoader());
System.out.println(
"class loader for DNSNameService: " + DNSNameService.class.getClassLoader());
System.out.println("class loader for this class: " + MyClassLoaderTest.class.getClassLoader());
System.out.println("class loader for Blob class: " + com.mysql.jdbc.Blob.class.getClassLoader());
}
}
Доступ для запуска приведенного выше кода показан на следующем рисунке:
Пример 2: пути к файлам, управляемые разными загрузчиками классов
С помощью следующей программы вы можете увидеть, что путь к файлу jar, отвечающий за каждый загрузчик классов, отличается:
public class JVMClassLoader {
public static void main(String[] args) {
System.out.println("引导类加载器加载路径:" + System.getProperty("sun.boot.class.path"));
System.out.println("扩展类加载器加载路径:" + System.getProperty("java.ext.dirs"));
System.out.println("系统类加载器加载路径:" + System.getProperty("java.class.path"));
}
}
Пример 3: команда загрузчика классов в Arthas
ArthasКоманда classloader представлена в , которую можно использовать для просмотра статистики, связанной с загрузчиком классов в текущем приложении, как показано на следующем рисунке.
- Таблица, отображаемая после входа в загрузчик классов, суммирует применяемые в настоящее время загрузчики классов, количество экземпляров каждого загрузчика классов и количество классов, загруженных каждым загрузчиком классов.
- После ввода classloader -t отображается иерархия загрузчика классов в текущем приложении.Видно, что BootStrap ClassLoader действительно находится на верхнем уровне системы загрузчика классов, за ним следует загрузчик классов расширения, а затем приложение загрузчик классов, здесь Существует также ArthasClassLoader, пользовательский загрузчик классов, реализованный Arthas.
Как работает модель родительского делегирования
Если загрузчик класса получает запрос на загрузку класса, он не будет пытаться сначала загрузить сам класс, а делегирует запрос загрузчику родительского класса для завершения Это относится к каждому уровню загрузчиков классов, поэтому все Запрос на загрузку должен в конечном итоге будет передан загрузчику классов запуска верхнего уровня, и только когда родительский загрузчик сообщит, что не может выполнить запрос на загрузку (необходимый класс не найден в его области поиска), дочерний загрузчик попытается загрузить себя.
Очевидным преимуществом использования родительской модели делегирования для организации отношений между загрузчиками классов является то, чтоКлассы Java имеют приоритетную иерархию вместе с их загрузчиком классов.. Например, класс java.lang.Object хранится в rt.jar. Независимо от того, какой загрузчик классов хочет загрузить этот класс, в конечном итоге он делегируется для загрузки загрузчику запускаемого класса в верхней части модели. класс загружается в программу Один и тот же класс присутствует в различных средах загрузчика классов . Напротив, если родительская модель делегирования не используется, и каждый загрузчик классов загружает ее сам, если пользователь напишет класс с именем java.lang.Object и поместит его в путь к классам программы, система появится в С несколькими различными классами объектов не гарантируется базовое поведение системы типов Java, и приложение станет хаотичным.
Реализация модели родительского делегирования очень проста Код для реализации родительского делегирования находится в методе loadClass() java.lang.ClassLoader, как показано в следующем коде:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查该类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException,
// 说明父类加载器无法完成加载请求
}
if (c == null) {
// 在父类加载器无法加载的时候,再调用本类的findClass方法进行类加载请求
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
// 当前类加载器是该类的define class loader
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
Нарушение модели родительского делегирования
загрузчик контекста потока
Как упоминалось выше, модель родительского делегирования решает проблему унификации базовых классов каждого загрузчика классов (более базовые классы загружаются загрузчиком верхнего уровня) Что делать, если базовому классу необходимо вызвать пользовательский класс? Очень классический пример — класс управления драйверами SQL — java.sql.DriverManager.
java.sql.DriverManager — это стандартная служба Java.Этот класс помещается в rt.jar, поэтому он загружается загрузчиком классов запуска, но при запуске приложения управление классом драйвера должно быть загружено разными поставщиками баз данных. драйвер, но загрузчик класса запуска не может найти эти конкретные классы реализации.Чтобы решить эту проблему, группа разработчиков Java предлагает менее элегантный дизайн: загрузчик контекста потока (Thread Context ClassLoader). Этот загрузчик класса можно установить с помощью метода setContextClassLoader() класса java.lang.Thread. Если он не был установлен при создании потока, он наследует один из родительского потока. Если глобальная область приложения не задано Если да, то загрузчик классов является загрузчиком классов приложения.
С помощью загрузчика контекста потока вышеуказанная проблема может быть решена — загрузчик родительского класса должен запросить загрузчик дочернего класса для завершения действия по загрузке класса, что фактически нарушает правила загрузки родительского делегирования.
Анализ исходного кода
Далее, давайте возьмем java.sql.DriverManager в качестве примера, чтобы увидеть использование загрузчика контекста потока.В статическом блоке ниже класса java.sql.DriverManager это запись для загрузки драйвера JDBC.
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
Глядя вниз на метод loadInitialDrivers(), место для использования загрузчика контекста потока находится в ServiceLoader.load
private static void loadInitialDrivers() {
// ……省去别的代码
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
//…… 省去别的代码
Код метода ServiceLoader.load выглядит следующим образом: JDBC sqlDriverManager — это полученный здесь загрузчик контекста, который управляет кодом пользователя для загрузки указанного класса.
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程中的上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
Итак, когда настроен этот загрузчик контекста? Ранее мы упоминали:
Этот загрузчик класса можно установить с помощью метода setContextClassLoader() класса java.lang.Thread. Если он не был установлен при создании потока, он наследует один из родительского потока. Если глобальная область приложения не задано Если да, то загрузчик классов является загрузчиком классов приложения.
Посмотрите на метод setContextClassLoader(), и, наконец, мы нашли следующий код в Launcher:
public class Launcher {
//……省去别的代码
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
//……省去别的代码
}
}
Суммировать
В этой статье мы рассмотрели родительскую модель делегирования загрузчика классов, рабочий процесс родительской модели делегирования, а также необходимость и анализ исходного кода для взлома родительской модели делегирования. В конце первой части мы также продемонстрировали использование команд загрузчика классов в Arthas, что можно учитывать при непосредственном устранении неполадок.
использованная литература
- woohoo.journal Dev.com/349/Java — кроме...
- Блог woohoo.cn на.com/Joemycin/afraid/93…
- "Глубокое понимание виртуальной машины Java"
- «Руководство по разработке эффективного кода на Java»
В этом выпуске основное внимание уделяется таким темам, как серверные технологии, устранение неполадок и оптимизация JVM, вопросы на собеседованиях по Java, личностный рост и самоуправление, а читателям предлагается опыт работы и роста передовых разработчиков. .