Это третий день моего участия в августовском испытании обновлений. Узнайте подробности мероприятия: Испытание августовского обновления
загрузчик классов
ClassLoader (ClassLoader): Его роль заключается в чтении двоичных данных из файла .class класса в память, помещении их в область методов области данных времени выполнения, а затем преобразовании в объект класса java.lang.Class. пример. Спецификация JVM позволяет загрузчикам классов предварительно загружать класс перед его использованием.
Механизм загрузки классов Процесс загрузки классов
Когда запускается программа java, именно JDK выполняет команду java, указывает полное имя класса, включая основной метод, и путь к классу в качестве записи программы, а затем ищет и загружает класс в соответствии с полным квалифицированное имя класса и находит. Правило заключается в том, чтобы искать в системных классах и в указанном пути к файлу. Если он находится в корневом каталоге файла класса, непосредственно проверьте, есть ли соответствующие подкаталоги и файлы классов.Если текущий путь является файлом jar, сначала выполните распаковку, а затем перейдите в каталог, чтобы проверить, есть ли соответствующий класс . В процессе поиска и загрузки класс, отвечающий за завершение операции, является загрузчиком класса ClassLoader.Входные данные представляют собой полное имя класса, а выходные данные представляют собой соответствующий объект Class.Загрузчиками классов являются следующие:
BootstrapClassLoader: он реализован внутри виртуальной машины Java.Этот класс отвечает за загрузку основных классов Java, то есть всех типов в каталоге «JAVA_HOME/lib» или всех типов в пути, указанном «-Xbootclasspath». Такие как String, Array и другие классы, а также rt.jar в каталоге папки lib в папке jdk
ExtClassLoader: унаследован от абстрактного класса ClassLoader, классом реализации по умолчанию является класс ExtClassLoader в пакете sun.misc.Launcher, который по умолчанию отвечает за загрузку некоторых расширенных jar-файлов в JDK, таких как jar-файлы в каталоге ext в папка с библиотекой
ExtClassLoader: отвечает за загрузку всех типов в каталоге «JAVA_HOME/lib/ext».
AppClassLoader: классом реализации загрузчика классов ClassLoader по умолчанию является класс AppClassLoader в пакете sun.misc.Launcher.Этот загрузчик отвечает за загрузку классов приложения по умолчанию, включая классы, реализованные им самим, и импортированные сторонние библиотеки классов. , то есть будет загружен весь класс Все указанные классы в директории программы java.
процесс загрузки класса
Полный процесс загрузки класса должен пройти три этапа загрузки, подключения и инициализации.
нагрузка
Основная цель JVM на данном этапе — преобразовать байт-коды из разных источников данных (может быть, файлов классов, пакетов jar или даже сети) в потоки двоичных байтов и загрузить их в память, а также сгенерировать представление класса java. lang.Класс объектов.
По сравнению с другими этапами загрузки класса, этап загрузки (точнее, действие по получению двоичного потока байтов класса на этапе загрузки) является наиболее контролируемым этапом, поскольку он может использовать предоставляемый системой загрузчик классов для Чтобы завершить загрузку, вы также можете настроить загрузчик классов для завершения загрузки. После завершения фазы загрузки двоичный поток байтов вне виртуальной машины сохраняется в области методов в соответствии с форматом, требуемым виртуальной машиной, а также создается объект класса java.lang.Class в куче Java, так что Объекты получают доступ к этим данным в области методов.
проверять
На этом этапе JVM проверит двоичный поток байтов, и только те, которые соответствуют спецификации байт-кода JVM, могут быть правильно выполнены JVM. Этот этап является важным барьером для обеспечения безопасности JVM, и ниже приведены некоторые из основных проверок. Убедитесь, что формат двоичного потока байтов соответствует ожидаемому, что все методы соответствуют ключевым словам управления доступом, что количество и тип параметров вызова метода правильные, что переменные правильно инициализированы перед использованием и что переменным присвоен соответствующий тип. , значение.
Подготовить
На этом этапе JVM выделяет память для переменных класса (также известных как статические переменные, измененные с помощью ключевого слова static) и инициализирует их (в соответствии с начальным значением типа данных по умолчанию, таким как 0, 0L, null, false и т. д. .).
То есть, если есть такой кусок кода:
public String test1 = "掘金1";
public static String test2 = "掘金2";
public static final String test3 = "掘金3";
test1 不会被分配内存,而 test2 会;但 test2 的初始值不是“王二”而是 null。
Следует отметить, что переменные, модифицированные static final, называются константами, которые отличаются от переменных класса. Константа не изменится после назначения, поэтому значение test3 на этапе подготовки — «Самородок 3», а не нуль.
Разобрать
На этом этапе символические ссылки в пуле констант преобразуются в прямые ссылки.
Символическая ссылка описывает цель, на которую ссылаются, как набор символов (литерал любой формы, если его можно использовать для однозначного определения цели). Во время компиляции класс Java не знает фактического адреса класса, на который ссылаются, поэтому вместо этого он может использовать только символические ссылки. Прямая ссылка разрешает символическую ссылку, чтобы найти фактический адрес памяти ссылки.
инициализация
Эта фаза является последним шагом в процессе загрузки класса. На этапе подготовки переменной класса было присвоено начальное значение по умолчанию, а на этапе инициализации переменной класса будет присвоено значение, ожидаемое кодом. Другими словами, фаза инициализации — это процесс выполнения методов конструктора класса.
Например
String juejin = new String("掘金");
В приведенном выше коде используетсяnew
для создания экземпляра строкового объекта, то в это время будет вызываться конструктор класса String для создания экземпляра juejin.
Модель родительского делегирования
Если загрузчик класса получает запрос на загрузку класса, он сначала делегирует запрос загрузчику верхнего уровня для завершения, а загрузчик верхнего уровня делегирует загрузчик верхнего уровня загрузчику верхнего уровня. Весь путь до загрузчика классов верхнего уровня; если загрузчик верхнего уровня не может завершить загрузку класса, текущий загрузчик классов попытается загрузить класс сам. Очевидным преимуществом использования родительской модели делегирования является то, что классы Java вместе с их загрузчиком классов имеют иерархию приоритетов, что важно для обеспечения стабильной работы программ Java.
Смысл в том, что если загрузчики двух классов разные, даже если два класса исходят из одного и того же файла байт-кода, то два класса не должны быть равными — модель родительского делегирования может гарантировать, что один и тот же класс в конечном итоге будет использоваться определенный класс.Загрузчик загружает. Это предотвращает появление в памяти нескольких копий одного и того же байт-кода и обеспечивает безопасную и стабильную работу программ Java.
пользовательский загрузчик классов
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) {
c = parent.loadClass(name, false);
} else {
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;
}
}
Это метод loadClass в ClassLoader, общий процесс выглядит следующим образом: 1) Проверить, загрузился ли класс, если да, то перезагружать его не нужно;
2) Если он не загружен, загрузить его через родительский класс (рекурсивно в свою очередь) или запустить загрузчик классов (бутстрап);
3) Если он еще не найден, вызвать метод findClass этого загрузчика;
Загрузчик класса загружается родительским классом.Если родительский класс не найден, загружается загрузчик. Во время реализации метода LoadClass будет передан индикатор разрешения.Этот монтаж и параметр инициализации forname совпадают, и он используется для определения того, выполняется ли блок кода инициализации после выполнения загрузки класса, но мы смотрим на вышеприведенный метод В этот момент значение передачи по умолчанию равно false, то есть загружается только класс Class, а класс части блока кода инициализации не вызывается. Код теста выглядит следующим образом:
загруженный класс
public class Juejin {
public Juejin() {
System.out.println("Juejin:" + getClass().getClassLoader());
System.out.println("Juejin Parent:" + getClass().getClassLoader().getParent());
}
public String print() {
System.out.println("Juejin:print()");
return "JuejinPrint";
}
}
TClassLoader:
class TClassLoader extends ClassLoader {
private String classPath;
public TClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
//获取.class的字节流
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
// 字节流解密
data = DESInstance.deCode("1234567890qwertyuiopasdf".getBytes(), data);
return data;
}
}
@Test
public void testClassLoader() throws Exception {
TClassLoader myClassLoader = new HClassLoader("D:/demo/a");
Class clazz = myClassLoader.loadClass("com.Demo.Juejin");
Object o = clazz.newInstance();
Method print = clazz.getDeclaredMethod("print", null);
print.invoke(o, null);
}