в моемГлубокое понимание механизма загрузки классов JVM.В части о загрузчике классов я немного рассказал, в этой статье вы подробно узнаете, как работают загрузчики классов в Java.
загрузчик классов
Требуется первый этап загрузки классаПолучить двоичный поток байтов, описывающий класс по его полному имени, модуль, который реализует это действие, является загрузчиком классов.
Хотя загрузчик класса реализует только загрузку класса, его роль в программе Java гораздо шире. Уникальность класса в Java зависит не только от самого класса, но и от его загрузчика. С точки зрения непрофессионала: сравните, являются ли два классаравный, это имеет смысл только в том случае, если два класса загружаются одним и тем же загрузчиком классов, в противном случае, даже если два класса происходят из одного и того же файла классов и загружаются одной и той же виртуальной машиной, пока загрузчики классов разные, два классы тоже не равны.
Давайте взглянем на фрагмент кода из книги г-на Чжоу Чжимина:
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is==null) {
return super.loadClass(name);
}
byte[] b =new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("ClassLoaderTest").newInstance();
System.out.println(obj.getClass()); //class ClassLoaderTest
System.out.println(obj instanceof ClassLoaderTest); //false
}
}
После аннотации выводится выходное содержимое.Мы используем загрузчик классов, определенный нами, для загрузки созданного нами файла класса, генерируем класс класса, а затем создаем экземпляр объекта obj. Из первого напечатанного предложения obj действительно является объектом, созданным ClassLoaderTest, но второе предложение возвращает false, потому что в системе есть два класса ClassLoaderTest, один из которыхЗагрузчик классов системных приложенийзагружен, один из наспользовательский загрузчик классовЗагружается хоть и из одного class файла, но двух классов.
Выше мы упоминали загрузчики приложений, пользовательские загрузчики классов и т. д. Не волнуйтесь, продолжайте читать.
Модель родительского делегирования
Что такое модель родительского делегирования, также следует часто спрашивать на собеседованиях. Мы также упоминали ранее, что в JVM есть только два типа загрузчиков классов:
- Bootstrap ClassLoader: реализация C++, часть виртуальной машины
- Пользовательский загрузчик классов (User-Defined Class Loader): реализация языка Java, независимая от внешней среды виртуальной машины, и как унаследованная, так и
java.lang.ClassLoader
Вообще говоря, при обсуждении загрузчиков классов мы будем делить его более мелко, мы можем посмотреть на следующий рисунок:
Следующее обсуждение будет основано на этой картине.
Большинство программ на Java используют загрузчики классов, предоставляемые следующими тремя системами.
-
Bootstrap ClassLoader: Этот класс будет отвечать за размещение
<JAVA_HOME>\lib\
в каталоге или-Xbootclasspath
Библиотека классов по пути, указанному в каталоге, указанном параметром и распознанном виртуальной машиной, загружается в память виртуальной машины, например, rt.jar, который распознается только по имени файла.Если имя не match даже в этом каталоге тоже не загрузится. Java-программа не может напрямую ссылаться на загрузчик классов начальной загрузки.Если пользователю необходимо делегировать запрос на загрузку загрузчику классов начальной загрузки при написании пользовательского загрузчика классов, он может напрямую заменить его нулевым значением. -
Расширение ClassLoader: Этот загрузчик классов предоставляется
sun.misc.Launcher $ExtClassLoader
реализация, отвечающая за загрузку<JAVA_HOME>\lib\ext
в каталоге или черезjava.ext.dirs
Все библиотеки классов по пути, указанному системной переменной. -
Загрузчик классов приложений: Этот загрузчик классов создан
sun.misc.Launcher $App-ClassLoader
выполнить. Загрузчик создается ClassLoadergetSystemClassLoader()
Метод возвращает значение, поэтому его обычно называют загрузчиком системного класса. Как правило, он загружает библиотеку классов, указанную пользователем в пути к классам (ClassPath). Разработчики обычно используют этот загрузчик классов напрямую. Если загрузчик классов не определен, класс, загруженный приложением, является загрузчиком классов по умолчанию в программе.
Давайте объясним, что такое модель родительского делегирования?
Модель родительского делегированиятребуется все, кроме верхнего уровняBootstrap ClassLoader
Кроме того, все другие загрузчики классов должны иметь свои собственные загрузчики родительских классов. Отношения родитель-потомок здесь обычно реализуются не наследованием, а комбинацией. Его основной рабочий процесс:Если загрузчик класса получает запрос на загрузку класса, он не будет пытаться сначала загрузить сам класс, а делегирует запрос загрузчику родительского класса, что имеет место для каждого уровня загрузчиков классов, поэтому, в конце концов, все запросы будут быть передан загрузчику класса запуска верхнего уровня.Только когда родительский загрузчик вернет, что он не может выполнить запрос на загрузку (то есть нужный класс не найден в его области поиска), дочерний загрузчик попытается загрузить его самостоятельно.
Каковы преимущества использования модели родительского делегирования?
Самым непосредственным преимуществом использования модели родительского делегирования является то, что классы Java имеют иерархию приоритетов вместе с их загрузчиками классов, такими какjaba.lang.Object
класс, который хранится вrt.jar
, независимо от того, какой загрузчик классов хочет загрузить этот класс, он в конечном итоге делегирует его на верхний уровень.boostrap ClassLoader
, поэтому класс Object является одним и тем же классом в различных средах загрузчика классов программы. Если модель родительского делегирования не используется, и каждый класс загружает Object, то в системе будут появляться различные версии класса Object, вызывая путаницу во всей системе.
Давайте посмотрим на режим родительского делегирования из исходного кода ClassLoader:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name); //检查该类是否加载过了
if (c == null) {//没加载过的情况
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果自定义的类加载器的parent不为null,就调用parent的loadClass进行加载类
c = parent.loadClass(name, false);
} else {
//否则就去找bootstrap ClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
Из приведенного выше кода загрузка класса с указанным именем делится на следующие 3 шага:
- перечислить
findLoadedClass(String)
проверить, загружен ли класс; - вызвать родительский класс
loadClass
метод, если родительский класс пуст, вызовите встроенный загрузчик классов начальной загрузки виртуальной машины для загрузки; - перечислить
findClass(String)
найти класс.
потому чтоloadClass
Он инкапсулирует модель родительского делегирования, поэтому при разработке собственного загрузчика классов стандарт Java рекомендует переопределитьfindClass()
метод.
Обычно виртуальная машина Java загружает класс из файловой системы, но некоторые классы не обязательно из файла, они также могут поступать из других источников, таких как сеть, зашифрованные файлы и т. д. Предположим, мы пишем собственный класс Загрузчик, загрузите файл класса, загруженный сервером.
Пример кода использования:
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
При определении мы переопределяем метод findClass:
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
. . .
}
}
Важно вот ещеdefineClass
Функция используется для преобразования набора двоичных байтов в экземпляр класса, который преобразуется в класс, а затем передается последующему процессу загрузки класса для разбора. Следующие шаги возвращаются кГлубокое понимание механизма загрузки классов JVM.описано в .