Так называемый загрузчик классов (Class Loader) предназначен для загрузки классов Java в виртуальную машину Java.Интервьюер, больше не спрашивайте меня "Механизм загрузки класса виртуальной машины Java""Введен механизм загрузки файлов классов. В этой статье мы сосредоточимся на загрузчиках и механизме родительского делегирования.
загрузчик классов
В JVM существует три типа ClassLoader: загрузчик класса запуска (или корневого класса) (Bootstrap ClassLoader), загрузчик класса расширения (ExtClassLoader) и загрузчик класса приложения (AppClassLoader). За загрузку классов в разных регионах отвечают разные загрузчики классов.
Запустите загрузчик классов: этот загрузчик не является классом Java, а реализован базовым C++ и отвечает за сохранение библиотеки классов в каталоге lib в JAVA_HOME, например rt.jar. Таким образом, загрузчик классов запуска не принадлежит библиотеке классов Java, и программы Java не могут напрямую ссылаться на него.Когда пользователи пишут собственный загрузчик классов, если им нужно делегировать запрос на загрузку загрузчику классов загрузки, они могут напрямую использовать null вместо.
Расширенный загрузчик классов: реализован sun.misc.Launcher$ExtClassLoader, отвечает за загрузку всех библиотек классов в каталог libext в JAVA_HOME или по пути, указанному системной переменной java.ext.dirs, разработчики могут напрямую использовать расширенный загрузчик классов. .
Загрузчик классов приложений: реализован sun.misc.Launcher$AppClassLoader. Поскольку этот загрузчик классов является возвращаемым значением метода getSystemClassLoader в ClassLoader, его также называют системным загрузчиком классов. Он отвечает за загрузку библиотеки классов, указанной в пути к классам пользователя, и может использоваться напрямую. Если загрузчик классов не настроен, по умолчанию используется этот загрузчик классов.
Таким образом можно распечатать путь загрузки и соответствующий JAR.
System.out.println("boot:" + System.getProperty("sun.boot.class.path"));
System.out.println("ext:" + System.getProperty("java.ext.dirs"));
System.out.println("app:" + System.getProperty("java.class.path"));
В распечатанном журнале вы можете увидеть подробный путь и библиотеки классов, включенные в этот путь. Из-за большого количества печатного контента он не будет здесь показан.
инициализация загрузчика классов
Помимо загрузчика классов запуска, загрузчик класса расширения и загрузчик класса приложения инициализируются через класс sun.misc.Launcher, а класс 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);
//...
}
Модель родительского делегирования
Модель родительского делегирования: когда загрузчик класса получает запрос на загрузку класса, он сначала рекурсивно запрашивает загрузчик родительского класса для загрузки, а когда загрузчик родительского класса не может найти класс (согласно полному имени класса), subclass Загрузчик попытается загрузить.
Отношение родитель-потомок в родительском делегировании обычно не реализуется путем наследования, а использует комбинированное отношение для повторного использования кода родительского загрузчика.
Написав тестовый код и отладив его, вы можете найти взаимосвязь между различными загрузчиками классов в процессе родительского делегирования.
И этот процесс будет более наглядно виден при заимствовании диаграммы последовательности.
Исходный код loadClassКласс ClassLoader является абстрактным классом, но он не содержит абстрактных методов. Пользовательский загрузчик классов можно реализовать, унаследовав класс ClassLoader и переопределив метод findClass. Но если вы нарушаете описанную выше модель родительского делегирования, чтобы реализовать собственный загрузчик классов, вам необходимо унаследовать класс ClassLoader и переопределить метод loadClass и метод findClass.
Часть исходного кода класса 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) {
//如果当前类没有被加载且父类加载器不为null,则请求父类加载器进行加载操作
c = parent.loadClass(name, false);
} else {
//如果当前类没有被加载且父类加载器为null,则请求根类加载器进行加载操作
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
//如果父类加载器加载失败,则由当前类加载器进行加载,
c = findClass(name);
//进行一些统计操作
// ...
}
}
//初始化该类
if (resolve) {
resolveClass(c);
}
return c;
}
}
Приведенный выше код также представляет иерархические и композиционные отношения между различными загрузчиками классов.
Зачем использовать модель родительского делегирования
Модель родительского делегирования предназначена для обеспечения безопасности типов в базовой библиотеке Java. Всем приложениям Java требуется как минимум ссылка на класс java.lang.Object, который необходимо загрузить в виртуальную машину Java во время выполнения. Если процесс загрузки выполняется настраиваемым загрузчиком классов, может существовать несколько несовместимых версий класса java.lang.Object.
Благодаря модели родительского делегирования загрузка классов базовой библиотеки Java унифицируется загрузчиком классов при запуске, что гарантирует, что все приложения Java используют одну и ту же версию классов базовой библиотеки Java и совместимы друг с другом.
загрузчик класса контекста
Оба загрузчика дочерних классов сохраняют ссылку на загрузчик родительского класса. Но что, если класс, загруженный загрузчиком родительского класса, должен получить доступ к классу, загруженному загрузчиком дочернего класса? Самый классический сценарий — загрузка JDBC.
JDBC — это набор стандартных интерфейсов для доступа к базам данных, разработанных Java, он включен в библиотеку базовых классов Java и загружается корневым загрузчиком классов. Библиотека классов реализации каждого поставщика базы данных вводится как сторонняя зависимость, и эта часть библиотеки классов реализации загружается загрузчиком классов приложения.
Код для подключения к Mysql:
//加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//连接数据库
Connection conn = DriverManager.getConnection(url, user, password);
DriverManager загружается загрузчиком класса запуска, а используемый им драйвер базы данных (com.mysql.jdbc.Driver) загружается загрузчиком класса приложения.Это типичный класс, загружаемый загрузчиком родительского класса, к которому должен обращаться дочерний класс class, загруженный сервером.
Этот процесс реализован, см. исходный код класса DriverManager:
//建立数据库连接底层方法
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
//获取调用者的类加载器
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
//由启动类加载器加载的类,该值为null,使用上下文类加载器
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
//...
for(DriverInfo aDriver : registeredDrivers) {
//使用上下文类加载器去加载驱动
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
//加载成功,则进行连接
Connection con = aDriver.driver.connect(url, info);
//...
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
}
//...
}
}
В приведенном выше коде обратите внимание на новую строку кода:
callerCL = Thread.currentThread().getContextClassLoader();
Эта строка кода получает ContextClassLoader из текущего потока, и где установлен ContextClassLoader? Он установлен в приведенном выше исходном коде Launcher:
// 设置上下文类加载器
Thread.currentThread().setContextClassLoader(this.loader);
Таким образом, так называемый загрузчик класса контекста по сути является загрузчиком класса приложения. Следовательно, загрузчик классов контекста — это только концепция, предложенная для решения обратного доступа к классам, а не совершенно новый загрузчик классов, а по сути загрузчик классов приложения.
пользовательский загрузчик классов
Пользовательскому загрузчику классов нужно только наследовать класс java.lang.ClassLoader, а затем переписать метод findClass(String name), указав, как получить поток байт-кода класса в методе.
Если вы хотите нарушить спецификацию родительского делегирования, вам нужно переписать метод loadClass (конкретная логическая реализация родительского делегирования). Но это не рекомендуется.
public class ClassLoaderTest extends ClassLoader {
private String classPath;
public ClassLoaderTest(String classPath) {
this.classPath = classPath;
}
/**
* 编写findClass方法的逻辑
*
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取类的class文件字节数组
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
// 生成class对象
return defineClass(name, classData, 0, classData.length);
}
}
/**
* 编写获取class文件并转换为字节码流的逻辑
*
* @param className
* @return
*/
private byte[] getClassData(String className) {
// 读取类文件的字节
String path = classNameToPath(className);
try {
InputStream is = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
// 读取类文件的字节码
while ((num = is.read(buffer)) != -1) {
stream.write(buffer, 0, num);
}
return stream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 类文件的完全路径
*
* @param className
* @return
*/
private String classNameToPath(String className) {
return classPath + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
public static void main(String[] args) {
String classPath = "/Users/zzs/my/article/projects/java-stream/src/main/java/";
ClassLoaderTest loader = new ClassLoaderTest(classPath);
try {
//加载指定的class文件
Class<?> object1 = loader.loadClass("com.secbro2.classload.SubClass");
System.out.println(object1.newInstance().toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
распечатать результат:
SuperClass static init
SubClass static init
com.secbro2.classload.SubClass@5451c3a8
О SuperClass и SubClass в предыдущей статье »Интервьюер, больше не спрашивайте меня "Механизм загрузки класса виртуальной машины Java""Код уже был опубликован, поэтому он не будет размещен здесь.
Из приведенного выше кода видно, что пользовательский загрузчик классов реализован в основном путем перезаписи пути findClass для получения класса.
Итак, в каких сценариях можно использовать собственный загрузчик классов? Когда реализация загрузчика классов, предоставляемая JDK, не может удовлетворить наши потребности, нам нужно только реализовать собственный загрузчик классов. Например, OSGi, горячее развертывание кода и другие области.
Модификация загрузчика классов Java9
Приведенная выше модель загрузчика классов — это предыдущая версия Java8, а загрузчик классов изменился в Java9. Здесь мы в основном кратко представляем изменения связанных моделей, и подробности конкретных изменений здесь не будут раскрываться.
Изменения каталога в java9.
Изменения в загрузчике классов в Java 9.
В java9 загрузчик класса приложения может делегировать загрузчику класса платформы, а также загрузчику класса запуска; загрузчик класса платформы может делегировать загрузчику класса запуска и загрузчику класса приложения.
В java9 загрузчик классов при запуске реализуется библиотекой классов и кодом на виртуальной машине. Для обратной совместимости он по-прежнему представлен в программе нулевым значением. Например, Object.class.getClassLoader() по-прежнему возвращает null. Однако не все модули платформы JavaSE и JDK загружаются загрузчиком классов запуска.
Модули, загружаемые загрузчиком классов запуска, — это java.base, java.logging, java.prefs и java.desktop, и это лишь некоторые из них. Другие модули платформы JavaSE и JDK загружаются загрузчиком классов платформы и загрузчиком классов приложений.
Для указания загрузочного пути к классам параметры -Xbootclasspath и -Xbootclasspath /p, а также системное свойство sun.boot.class.path больше не поддерживаются в java9. Параметр -Xbootclasspath/a по-прежнему поддерживается, и его значение хранится в системном свойстве jdk.boot.class.path.append.
Механизм расширения больше не поддерживается в java9. Однако он сохраняет загрузчик класса расширения под новым именем, называемым загрузчиком класса платформы. Класс ClassLoader содержит статический метод getPlatformClassLoader(), который возвращает ссылку на загрузчик классов платформы.
резюме
В этой статье в основном представлены загрузчик классов виртуальной машины Java и механизм родительского делегирования на основе java8, а также некоторые изменения в Java8. Среди них более глубокие изменения в java9 можно дополнительно изучить. Эта серия постоянно обновляется, приглашаем подписаться на публичный аккаунт WeChat «Program New Vision».
Оригинальная ссылка: "Загрузчик классов виртуальной машины Java и родительский механизм делегирования》
Серия статей "Интервьюер":
- "Подробное объяснение структуры памяти JVM》
- "Интервьюер, перестаньте спрашивать меня "Механизм сборки мусора Java GC"》
- "Интервьюер, Структура памяти Java8 JVM изменилась, постоянное преобразование в метапространство》
- "Интервьюер, перестаньте спрашивать меня "сборщик мусора Java"》
- "Загрузчик классов виртуальной машины Java и родительский механизм делегирования》