Процесс загрузки класса серии JVM - пользовательский загрузчик классов
Если честно, автор процесса загрузки класса относительно знаком и имеет практический опыт, потому что существует практический опыт пользовательского погрузчика класса (статья будет разделена с вами в конце), хотя большинство маленьких партнеров считают, что Эта часть не имеет практической значимости для кодирования, если вы пишете CRUD и используя существующие бизнес-структуры языка высокого уровня, я могу вам сказать, это действительно не полезно. Но опять же, если вы хотите узнать больше о нижнем слое и сделать некоторые трюки при нагрузке класса, то это необходимый учащийся. Многие рамки используют механизм загрузки классадинамическая нагрузкаФункции для работы, такие как хорошо известная модульность OSGI (один модуль, один загрузчик классов), JSP (преобразуется в поток байтов во время выполнения для динамической загрузки загрузчика), Tomcat (многие пользовательские загрузчики классов используются для изоляции разных проектов). )) ... здесь не указано. В этой статье сначала будет рассказано о процессе загрузки классов, а затем автор поделится опытом загрузки пользовательских классов.
Структура статьи
1 Объяснение каждого процесса загрузки класса
2 Объяснение пользовательского загрузчика классов
3 Фактический пользовательский загрузчик классов
1. Объяснение каждого процесса нагрузки класса
Автор нашел картинку в Интернете и нарисовал блок-схему жизненного цикла класса с привязкой к себе:
будь осторожен: Различные процессы на рисунке не являются строго последовательными.Например, при загрузке 1 уже началась проверка 2, и она осуществляется крест-накрест.
нагрузка
Проще говоря, фаза загрузки заключается в преобразовании нашего скомпилированного статического файла .Class в память (область методов), а затем в предоставлении доступа к нему программистам. В частности, расширить:
- Получите поток двоичных байтов, определяющий этот класс, через полное имя класса (это может быть файл .class, io в сети, zip-пакет и т. д.).
- Преобразуйте статическую структуру хранения, представленную этим потоком байтов, в структуру данных времени выполнения области метода.
- В памяти (реализация HotSpot фактически находится в области методов) объект java.lang.Class, представляющий этот класс, генерируется как запись доступа к различным данным этого класса в области методов.
проверять
Двоичный поток байтов, полученный на этапе загрузки, не обязательно исходит из файла .class, например, отправленного из сети, поэтому его нельзя загрузить без определенной проверки формата. Таким образом, этап проверки фактически предназначен для защиты JVM. Для общего Javaer мы все файлы .class скомпилированы из файлов .java, а затем преобразованы в соответствующие бинарные потоки, что не вредно. Так что не беспокойтесь слишком много об этой части.
Подготовить
Фаза подготовки в основном заключается в выделении памяти (в области методов) для статических переменных и установке начальных значений.
Например: общедоступное статическое целочисленное значение = 1; значение на этапе подготовки фактически равно 0. Обратите внимание, что константы назначаются на этапе подготовки:
общедоступное статическое окончательное целочисленное значение = 1; значение назначается 1 на стадии подготовки;
Разобрать
Фаза парсинга более абстрактна, о ней я расскажу немного, потому что она не очень важна, есть два понятия.Символическая ссылка,прямая цитата. Это немного популярно, но не очень точно.Например, new B() вызывается в классе A, подумайте об этом, после того, как мы скомпилируем файл .class, это соответствие все еще существует, но в виде инструкций байт-кода Exist, таких как "invokespecial #2" Вы можете догадаться, что #2 на самом деле является нашим классом B, тогда, когда эта строка кода выполняется, как JVM узнает, где находится инструкция, соответствующая #2, это статический парень, если класс B имеет был загружен в область метода, и адрес (# f00123), поэтому в это время этот # 2 должен быть преобразован в этот адрес (# f00123), чтобы JVM не знала, где находится класс B, когда он выполняется , чтобы позвонить. (Он настолько популярен, что я сомневаюсь в своей жизни.) Другие, такие как символическая ссылка методов и символическая ссылка констант, на самом деле имеют одно и то же значение. Каждый должен понимать, что так называемые методы, константы и классы — это все концепция уровня языков высокого уровня (Java), в файле .class неважно, кто вы,в виде инструкцииТаким образом, опорные отношения (которые называют тем, кто ссылается на кого), должны быть преобразованы в форму адресов. Хорошо. Достаточно просто. Давайте все понять. Этот кусок на самом деле не очень важен, для большинства кодеров, поэтому я расскажу об этом в целом.
инициализация
Эта часть на самом деле звонитконструктор класса, Обратите внимание, что это конструктор класса, а не конструктор экземпляра. Конструктор экземпляра — это конструктор, который мы обычно пишем. Конструктор класса генерируется автоматически. Правила генерации:
Операция присваивания статической переменной + блок статического кода
Собирать в порядке, в котором они появляются.
Уведомление:1Выделение памяти и инициализация статических переменных находятся на стадии подготовки.2Класс может выполняться одновременно многими потоками, а JVM будет блокироваться для обеспечения единственности, поэтому не выполняйте трудоемкие операции в статических блоках кода. Избегайте блокировки потока.
использовать и удалить
Использование заключается в том, что вы напрямую создаете новый или через отражение.newInstance.
Удаление происходит автоматически, и gc также будет переработан в районе Фангфа.Однако условия очень суровые.Если вам интересно, вы можете посмотреть сами.Как правило, классы не удаляются.
2. Объяснение пользовательского загрузчика классов
2.1 Загрузчик классов
Загрузчик классов должен выполнить вышеуказанноепроцесс загрузки классаДля некоторых классов в системе есть загрузчики по умолчанию, с точки зрения JVM есть только два типа загрузчиков:
- Bootstrap ClassLoader: реализован на языке C++ (для HotSpot), отвечает за сохранение в
<JAVA_HOME>
Библиотеки классов в каталоге /lib или по пути, указанному параметром -Xbootclasspath, загружаются в память. - Другие загрузчики классов: реализованы языком Java, унаследованы от абстрактного класса ClassLoader. как:
- Расширение ClassLoader: отвечает за загрузку
<JAVA_HOME>
Все библиотеки классов в каталоге /lib/ext или по пути, указанному в системной переменной java.ext.dirs. - Загрузчик классов приложений (Application ClassLoader). Отвечая за загрузку указанной библиотеки классов в пути к классам пользователя (classpath), мы можем использовать этот загрузчик классов напрямую. В общем, если у нас нет пользовательского загрузчика классов, этот загрузчик используется по умолчанию.
- Пользовательский загрузчик классов, который пользователь определяет в соответствии со своими потребностями. Он также должен наследоваться от ClassLoader.
- Расширение ClassLoader: отвечает за загрузку
2.2 Модель родительского делегирования
Если загрузчик класса получает запрос на загрузку класса, он не будет пытаться загрузить сам класс в первую очередь, а делегирует запрос загрузчику родительского класса. Это верно для каждого загрузчика классов, только если родительский загрузчик не может найти указанный класс в своей области поиска (т. е. ClassNotFoundException), дочерний загрузчик попытается загрузить его сам. Увидеть ниже:
Следует отметить, что пользовательский загрузчик классов может не следовать модели родительского делегирования, но транзитивное отношение в красной области на рисунке предопределено JVM и не может быть изменено кем-либо. Каковы преимущества модели родительского делегирования? Например, если кто-то преднамеренно определяет класс String в своем собственном коде, имя пакета и имя класса такие же, как те, что поставляются с JDK, то, согласно модели родительского делегирования, загрузчик класса сначала передаст его родительскому классу. загрузчика для загрузки, он в конечном итоге будет передан загрузчику класса запуска, и класс загрузки запуска определит, что он был загружен, поэтому класс String, настроенный программистом, не будет загружен.Избегайте программистов, которые сами произвольно изменяют классы системного уровня.
2.3 Пользовательский загрузчик классов
После долгого разговора о теории я не могу дождаться, чтобы начать код. Давайте посмотрим, как настроить загрузчик классов и как следовать модели родительского делегирования (восходящей транзитивности) при настройке загрузчика. На самом деле, это очень просто. JDK использует здесь шаблон проектирования шаблона и восходящую транзитивность. на самом деле помогло нам его инкапсулировать.Ну, это было реализовано в ClassLoader, в методе loadClass:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已经加载过。
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//2 .如果没有加载过,先调用父类加载器去加载
c = parent.loadClass(name, false);
} else {
// 2.1 如果没有加载过,且没有父类加载器,就用BootstrapClassLoader去加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//3. 如果父类加载器没有加载到,调用findClass去加载
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;
}
}
Как видно из приведенного выше кода, функция loadClass(String, boolean) реализует модель родительского делегирования! Общий процесс выглядит следующим образом:
- Проверить, был ли загружен класс с указанным именем, если он был загружен, его не нужно загружать снова, и он возвращается напрямую.
- Если этот класс не был загружен, то судите, есть ли родительский загрузчик, если есть родительский загрузчик, то он будет загружен родительским загрузчиком (то есть вызовите parent.loadClass(name, false);). класс начальной загрузки для загрузки устройства для загрузки.
- Если ни родительский загрузчик, ни загрузчик классов начальной загрузки не находят указанный класс, вызовите функцию текущего загрузчика классов.findClassметод для завершения загрузки класса. Класс поиска по умолчанию не работает и напрямую генерирует исключение ClassNotFound, поэтому наш пользовательский загрузчик классов переопределит этот метод.
- Можно догадаться, что findClass ApplicationClassLoader для загрузки переходит в путь к классам, а ExtentionClassLoader — в каталог java_home/lib/ext для загрузки. На самом деле метод findClass отличается..
Как видно из вышеизложенного, функция findClass абстрактного класса ClassLoader по умолчанию выдает исключение. Как мы знаем ранее, loadClass вызовет функцию findClass в нашем пользовательском загрузчике классов, когда родительский загрузчик не сможет загрузить класс, поэтому мы должны преобразовать указанное имя класса в объект класса в функции loadClass.
Это прекрасно работает, если вы читаете класс с указанным именем в виде массива байтов. Но как преобразовать массив байтов в объект класса? Очень просто, Java предоставляет метод defineClass, с помощью которого вы можете преобразовать массив байтов в объект класса~
defineClass: преобразование массива байтов в объект класса, этот массив байтов является окончательным массивом байтов после чтения файла класса.
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError {
return defineClass(name, b, off, len, null);
Принцип и несколько важных методов пользовательского загрузчика классов представлены выше (loadClass,findClass,defineClass), я полагаю, что большинство моих друзей все же ослепли, не беда, я сначала загружу картинку, а потом кастомный загрузчик классов:
Пример пользовательского загрузчика классов:
import java.io.InputStream;
public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
}
public MyClassLoader(ClassLoader parent)
{
//一定要设置父ClassLoader不是ApplicationClassLoader,否则不会执行findclass
super(parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
//1. 覆盖findClass,来找到.class文件,并且返回Class对象
try
{
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
//2. 如果没找到,return null
return null;
}
byte[] b = new byte[is.available()];
is.read(b);
//3. 讲字节数组转换成了Class对象
return defineClass(name, b, 0, b.length);
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
}
Только немного:
На самом деле это очень просто.Наследуйте объект ClassLoader и переопределите метод findClass.Функция этого метода состоит в том, чтобы найти файл .class, преобразовать его в массив байтов и вызвать объект defineClass, чтобы преобразовать его в объект класса. возвращаться. так легко..
Продемонстрируйте эффект:
MyClassLoader mcl = new MyClassLoader();
Class<?> c1 = Class.forName("Student", true, mcl);
Object obj = c1.newInstance();
System.out.println(obj.getClass().getClassLoader());
System.out.println(obj instanceof Student);
Возвращаемый результат:
sun.misc.Launcher$AppClassLoader@6951a712
true
MyClassLoader mcl = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent());
Class<?> c1 = Class.forName("Student", true, mcl);
Object obj = c1.newInstance();
System.out.println(obj.getClass().getClassLoader());
System.out.println(obj instanceof Student);
Возвращаемый результат:
MyClassLoader@3918d722
false
Ключевой анализ:
Единственная разница между первым кодом и вторым кодом заключается в том, что в new MyClassLoader() входящий ClassLoader.getSystemClassLoader().getParent(); (на самом деле этозагрузчик класса расширения)
-
Если это значение не передается, загрузчиком родительского класса по умолчанию является Application ClassLoader, тогда вы можете знать, что класс Student (класс Student в пути ClassPath) был загружен в этот загрузчик, и мы передаем класс при вызове Class.forName , После ввода пользовательского загрузчика классов он вызовет loadClass пользовательского загрузчика классов, определит, что он не был загружен ранее, а затем вызовет loadClass родительского класса (ApplicationClassLoader), и в результате он будет загружен. , поэтому он возвращается напрямую. Так что печатайте ClassLoader как AppClassLoader.
Убедитесь, что загрузчиком родительского класса по умолчанию является ApplicationClassLoader.:MyClassLoader mcl = new MyClassLoader(); System.out.println(mcl.getParent().getClass());
Результат печати: class sun.misc.Launcher$AppClassLoader
-
Когда мы передаем загрузчик родительского класса в качестве загрузчика расширенного класса, когда вызывается loadeClass родительского класса (загрузчик расширенного класса), поскольку загрузчик расширенного класса загружает только классы в каталоге java_home/lib/ext, путь к классам Если он не может быть загружен, он возвращает null.Согласно логике loadClass, для загрузки будет вызван пользовательский загрузчик класса findClass. Так что печатайте ClassLoader как MyClassLoader.
- Условие для того, чтобы instanceof возвращал true, состоит в том, что (загрузчик классов + класс) одинаковы., хотя здесь мы все класс Student, файл, но загруженный двумя загрузчиками классов, естественно, возвращает false.
- Единственным критерием для оценки класса в JVM является то, что (загрузчик классов + файл .class) одинаковы.Такими критериями являются instanceof и casts.
- Обратите внимание, что упомянутый здесь загрузчик родительского класса реализуется не путем наследования, а с помощью переменных-членов. Когда конструктор вызывается и передается, его собственная переменная-член parent устанавливается для входящего загрузчика.
- Внеклассные производные: здесь автор следует модели родительского делегирования, поэтому findClass охвачен, но loadClass не охвачен. Фактически, loadClass также может быть охвачен. Например, если вы переопределяете loadClass, реализация «непосредственно загружает файл без оценки того, был ли загружен родительский класс», поэтому он нарушает модель родительского делегирования, что обычно не рекомендуется. Но вы, ребята, можете попробовать.
Пользовательский загрузчик классов закончен для всех.Хотя автор считает, что это было объяснено ясно, потому что это не более чем проблема нескольких методов (loadClass, findClass, defineClass), но все же дать вам несколько порталов, вы можете прочитать больше и читайте друг друга. Взгляните на:
Блог Woohoo.cn на.com/Cactus730/Afraid/48…
www.importnew.com/24036.html
3. Фактический пользовательский загрузчик классов
Фактически, вышеизложенное в основном ясно объяснило пользовательский загрузчик классов.Здесь я поделюсь с вами реальным опытом автора по написанию собственного загрузчика классов. Фон выглядит следующим образом:
В проекте мы использовали коммуникационную среду с открытым исходным кодом, но из-за изменения исходного кода были внесены некоторые индивидуальные изменения.Предположим, что исходный код является версией A до изменения исходного кода и версией B после исходного кода. Поскольку некоторые коды в проекте должны использовать версию A, некоторые коды должны использовать версию B. Все имена пакетов и классов одинаковы в версии A и версии B. Затем возникает проблема: если вы полагаетесь только на ApplicationClassLoader для загрузки, он загрузит только версию, ближайшую к ClassPath. Когда оставшийся загружен, он сразу вернется к версии, которая была загружена в соответствии с родительской моделью делегирования. Итак, здесь вам нужно настроить загрузчик классов. Общая идея такова:
Здесь следует отметить, что вПри настройке загрузчика классов обязательно установите для загрузчика родительского класса значение ExtentionClassLoader., если не задано, в соответствии с моделью родительского делегирования загрузчиком родительского класса по умолчанию является ApplicationClassLoader. загруженная версия A будет возвращена напрямую вместо вызова findClass подкласса. Он не будет вызывать findClass нашего пользовательского загрузчика классов для удаленной загрузки версии B.
Кстати, план реализации автора здесь фактически состоит в том, чтобы следовать модели родительского делегирования.Если автор не следует модели родительского делегирования, он может напрямую настроить загрузчик классов, переопределить метод loadClass и не пускать его в родительский класс для проверки сначала, но изменить. Также можно напрямую вызвать метод findClass для загрузки версии B. Вы должны написать код гибко.
Эпилог
Что ж, механизм загрузки классов JVM был предоставлен вам, надеюсь, вы придумаете собственный загрузчик классов для решения актуальной проблемы. Хорошего дня .