Автор делит экологию Java на несколько модулей. В этой статье объясняется механизм загрузки классов модуля jvm. Чтобы изучить jvm, вы должны знать, как загружаются классы. Пространство немного длинное, но вы можете полностью понять процесс загрузки классов. освоив эту статью.
Предположим, есть такой класс
package com.manong.jvm;
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute() {
//一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
Взяв в качестве примера приведенный выше класс, давайте непосредственно посмотрим, как этот класс входит в jvm и запускается.
Процесс запуска выглядит следующим образом:
1. На этапе компиляции javac javac скомпилирует файлы java, которые мы написали, в файлы типов классов.
2. В системе Windows java.exe вызывает базовый файл jvm.dll для создания виртуальной машины.
3. Сначала виртуальная машина создает загрузчик классов начальной загрузки.
4. Класс sun.misc.Launcher (класс запуска) загружается загрузчиком классов начальной загрузки, сначала посмотрите исходный код этого класса:
//此源码只有相关的部分,并且省略了异常捕获相关的代码
public class Launcher {
private static Launcher launcher = new Launcher();
private ClassLoader loader;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
Launcher.ExtClassLoader var1;
var1=Launcher.ExtClassLoader.getExtClassLoader();
this.loader=Launcher.AppClassLoader.getAppClassLoader(var1);
Thread.currentThread().setContextClassLoader(this.loader);
}
public ClassLoader getClassLoader() {
return this.loader;
}
}
- Статические переменные в нем обеспечивают создание экземпляра Launcher после загрузки;
- В методе построения ExtClassLoader (расширенный загрузчик классов) и AppClassLoader (загрузчик классов приложений) окончательно генерируются, а AppClassLoader назначается загрузчику переменных-членов экземпляра Launcher;
- Предоставьте метод-член getClassLoader, вы можете получить загрузчик переменной-члена;
- Предоставьте статический метод getLauncher для получения текущего сгенерированного экземпляра Launcher.
5. Вызовите метод getClassLoader экземпляра Launcher, чтобы получить загрузчик класса приложения и начать загрузку класса.Обратите внимание, что независимо от того, какой класс загружен, для загрузки используется полученный загрузчик класса приложения, а для делегировать вверх;
6. Вызвать метод loadClass загрузчика классов приложения, чтобы перейти к этапу выбора загрузчика классов;
7. Вызовите метод findClass верхнего уровня, чтобы ввести ссылку для загрузки класса;
8. После загрузки начните выполнение кода;
Примечание. Загрузчик класса приложения AppClassLoader и загрузчик класса расширения ExtClassLoader на самом деле являются внутренним классом класса Launcher, вы можете перейти к исходному коду, чтобы увидеть это самостоятельно.
Выше описан процесс загрузки класса в jvm и его запуска Далее мы сосредоточимся на 6-м и 7-м пунктах выше.
1 Метод, вызываемый в пункте 6, в основном предназначен для выбора загрузчика классов для загрузки Давайте сначала поговорим о загрузчике классов в jvm.
Классификация загрузчиков классов в Java
- Загрузчик классов Bootstrap: отвечает за загрузку основных библиотек классов, расположенных в каталоге lib JRE, которые поддерживают работу JVM, например rt.jar, charsets.jar и т. д.
- Загрузчик класса расширения: отвечает за загрузку пакета класса JAR в каталог расширения ext в каталоге lib среды JRE, которая поддерживает работу JVM.
- Загрузчик классов приложений: отвечает за загрузку пакетов классов по пути ClassPath, в основном для загрузки тех классов, которые вы написали сами.
- Пользовательский загрузчик: отвечает за загрузку пакета класса по заданному пользователем пути.
public class TestJDKClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
运行结果:
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d
the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc
bootstrapLoader加载以下文件:
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/resources.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/rt.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/sunrsasign.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jsse.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jce.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/charsets.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jfr.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/classes
extClassloader加载以下文件:
D:\dev\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
appClassLoader加载以下文件:
D:\dev\Java\jdk1.8.0_45\jre\lib\charsets.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\deploy.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\javaws.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jce.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfr.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jsse.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\management-agent.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\plugin.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\resources.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\rt.jar;D:\ideaProjects\project-all\target\classes;C:\Users\zhuge.m2\repository\org\apache\zookeeper\zookeeper\3.4.12\zookeeper-3.4.12.jar;C:\Users\zhuge.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\zhuge.m2\repository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;C:\Users\zhuge.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\zhuge.m2\repository\jline\jline\0.9.94\jline-0.9.94.jar;C:\Users\zhuge.m2\repository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;C:\Users\zhuge.m2\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;C:\Users\zhuge.m2\repository\com\google\guava\guava\22.0\guava-22.0.jar;C:\Users\zhuge.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;C:\Users\zhuge.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations-2.0.18.jar;C:\Users\zhuge.m2\repository\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;C:\Users\zhuge.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar;D:\dev\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar
Приведенного выше кода достаточно, чтобы увидеть, какой класс загружается каждым загрузчиком классов.
Механизм родительского делегирования
Как упоминалось в предыдущих шагах, JVM использует экземпляр загрузчика классов AppClassLoader, возвращенный методом getClassLoader() Launcher, для загрузки нашего приложения по умолчанию. Тогда мы знаем, что классы, которые мы написали, загружаются AppClassLoader. С этим проблем нет. Эти библиотеки классов Как загрузить средний класс, зависит от родительского механизма делегирования.
При загрузке класса он сначала поручает родительскому загрузчику найти целевой класс, а затем доверяет верхнему родительскому загрузчику загрузить его, если он не может его найти.Найти и загрузить целевой класс в пути загрузки. Например, наш класс Math будет сначала загружен загрузчиком класса приложения, загрузчик класса приложения сначала делегирует загрузчик класса расширения для загрузки, загрузчик класса расширения затем делегирует загрузчик класса загрузки, а загрузчик класса загрузки верхнего уровня будет загружен в свой собственный Если класс Math не найден в пути загрузки класса в течение длительного времени, запрос на загрузку класса Math возвращается вниз.Расширенный загрузчик класса загружает его сам после получения ответа.Вернуть загрузку запрос класса Math к загрузчику классов приложений. Затем загрузчик классов приложений ищет класс Math в своем собственном пути загрузки классов, и когда он находит его, он загружает его сам. . Проще говоря, механизм родительского делегирования состоит в том, чтобы сначала найти отца, чтобы загрузить его, а затем загрузить сам сын.
Давайте взглянем на исходный код механизма родительского делегирования класса, загруженного загрузчиком класса приложения AppClassLoader. Метод loadClass класса AppClassLoader в конечном итоге вызовет метод loadClass своего родительского класса ClassLoader. Общая логика этого метода выглядит следующим образом. : Во-первых, проверьте, был ли загружен класс с указанным именем, если он загружен, вам не нужно снова загружать его и возвращаться напрямую. Если этот класс не был загружен, то судите, есть ли родительский загрузчик, если есть родительский загрузчик, то он будет загружен родительским загрузчиком (то есть вызовите parent.loadClass(name, false);). класс начальной загрузки для загрузки устройства для загрузки. Если ни родительский загрузчик, ни загрузчик классов начальной загрузки не находят указанный класс, вызовите метод findClass текущего загрузчика классов, чтобы завершить загрузку класса.
//ClassLoader的loadClass方法,里面实现了双亲委派机制
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 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();
//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
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;
}
Зачем разрабатывать механизм родительского делегирования?
- Механизм безопасности песочницы: класс java.lang.String.class, написанный вами, не будет загружен, что может предотвратить произвольное вмешательство в основную библиотеку API.
- Избегайте повторной загрузки классов: когда родитель уже загрузил класс, нет необходимости загружать его снова в ClassLoader, чтобы обеспечить уникальность загруженного класса
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("**************My String Class**************");
}
}
运行结果:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为......
Общий механизм делегирования ответственности «Полная ответственность» означает, что когда ClassLoder загружает класс, если явно не используется другой ClassLoder, классы, от которых зависит класс, и ссылки также загружаются этим ClassLoder.
Нарушение модели родительского делегирования
Модель родительского делегирования — это не модель с обязательными ограничениями, а реализация загрузчика классов, рекомендованная дизайнерами Java для разработчиков. В мире Java большинство загрузчиков классов следуют этой модели, но есть и исключения.До появления модульности Java модель родительского делегирования в основном «ломалась» в больших масштабах 3 раза.
Первая «ломка» модели родительского делегирования фактически произошла еще до появления модели родительского делегирования — «древняя» эпоха до выхода JDK 1.2. Поскольку модель родительского делегирования была введена после JDK 1.2, концепция загрузчика классов и абстрактный класс java.lang.ClassLoader уже существовали в первой версии Java, столкнувшись с существующим определяемым пользователем загрузчиком классов, разработчикам Java приходится идти на некоторые компромиссы, когда введение модели родительского делегирования. Чтобы быть совместимым с этими существующими кодами, больше невозможно избежать возможности перезаписи loadClass () подклассами техническими средствами. Только в java после JDK 1.2 Добавить новый защищенный метод findClass ( ) в .lang.ClassLoader и направлять написанную пользователем логику загрузки классов, чтобы максимально переписать этот метод вместо написания кода в loadClass(). Мы разобрали метод loadClass() в предыдущем разделе, здесь реализована специфическая логика родительского делегирования.Согласно логике метода loadClass(), если родительский класс не загружается, он автоматически вызывает свой собственный findClass( ) для завершения загрузки, чтобы он не мешал пользователям загружать классы в соответствии с их собственными пожеланиями, а также мог гарантировать, что вновь написанный загрузчик классов соответствует правилам родительского делегирования.
Второе «разрушение» модели родительского делегирования вызвано дефектами самой модели.Родительское делегирование решает проблему консистентности базовых типов, когда каждый загрузчик классов взаимодействует (более базовые классы загружаются верхним уровнем). типы называются «базовыми» потому, что они всегда существуют как API, которые наследуются и вызываются кодом пользователя, но часто не существует идеальных правил для разработки программ. что делать? Это не невозможно.Типичным примером является служба JNDI.JNDI теперь является стандартной службой в Java, и ее код загружается загрузчиком класса запуска (добавлен в rt.jar в JDK 1.3), должен быть очень базовым типом в Ява. Но цель JNDI состоит в том, чтобы находить и централизованно управлять ресурсами.Он должен вызвать код JNDI Service Provider Interface (SPI), реализованный другими производителями и развернутый под ClassPath приложения.Теперь возникает проблема, это невозможно для загрузчик класса запуска, чтобы распознать и загрузить эти коды, так что же нам делать? Чтобы решить эту дилемму, группе разработчиков Java пришлось представить менее элегантную конструкцию: загрузчик класса контекста потока (Thread Context ClassLoader). Этот загрузчик класса можно установить с помощью метода setContext-ClassLoader() класса java.lang.Thread.Если он не был установлен при создании потока, он унаследует один из родительского потока, если его нет в глобальная область приложения Если установлено, загрузчик классов по умолчанию является загрузчиком классов приложения. С загрузчиком класса контекста потока программа может делать некоторые "обманные" вещи. Служба JNDI использует этот загрузчик класса контекста потока для загрузки требуемого кода службы SPI. Это поведение загрузчика родительского класса, чтобы запросить загрузчик дочернего класса завершить загрузку класса. Это поведение фактически открывает родительскую модель делегирования. обратное использование загрузчиков классов, нарушило общие принципы модели родительского делегирования, но и беспомощная вещь. Загрузка с использованием SPI в Java в основном выполняется таким образом, например, JNDI, JDBC, JCE, JAXB и JBI. Однако, когда имеется более одного поставщика услуг SPI, код может только жестко закодировать суждение, основанное на типе конкретного поставщика.Чтобы устранить эту чрезвычайно неэлегантную реализацию, в JDK 6 JDK предоставляет java. Класс ServiceLoader, основанный на информации о конфигурации в META-INF/services, дополненный режимом цепочки ответственности, обеспечивает относительно разумное решение для загрузки SPI.
Третий «лом» модели родительского делегирования вызван стремлением пользователя к динамизму программы.Под «динамическим» здесь понимаются некоторые очень «горячие» термины: горячая замена кода (Hot Swap), горячее развертывание модуля (Hot Deployment). , и т.д. Проще говоря, я надеюсь, что приложения Java можно будет подключить к мыши и диску U, как периферийные устройства нашего компьютера, и их можно будет использовать сразу, без перезагрузки машины.Если есть проблема с мышью или если вы хотите обновить, вы нужно заменить мышь без выключения или перезагрузки. Для персональных компьютеров однократный перезапуск не имеет большого значения, но для некоторых производственных систем выключение и однократный перезапуск могут быть классифицированы как производственная авария.В этом случае горячее развертывание очень важно для разработчиков программного обеспечения, особенно для больших систем.Или предприятия Разработчики программного обеспечения начального уровня очень привлекательны.
Хотя слово «сломанный» используется здесь для описания описанного выше поведения, которое не соответствует принципам модели родительского делегирования, слово «сломанный» здесь не обязательно является уничижительным. Нарушение старых принципов, несомненно, является новшеством, если есть четкая цель и веская причина. Точно так же, как дизайн загрузчика классов в OSGi не соответствует традиционной архитектуре загрузчика классов с делегированным родителем, и в отрасли все еще много споров по поводу дополнительной высокой сложности, связанной с горячим развертыванием, но есть понимание этот аспект Технический персонал 2018 года в основном может прийти к консенсусу в том, что использование загрузчика классов в OSGi стоит изучения, и если они полностью понимают реализацию OSGi, даже если они освоят суть загрузчика классов.
Пример пользовательского загрузчика классов: Пользовательский загрузчик классов должен наследовать только класс java.lang.ClassLoader, который имеет два основных метода:
- Одним из них является loadClass(String, boolean), который реализует механизм родительского делегирования;
- Один метод — findClass, реализация по умолчанию — пустой метод.
Таким образом, наш пользовательский загрузчик классов в основном переписывает метод findClass, но если мы не хотим использовать механизм родительского делегирования, мы можем сделать это, переписав loadClass.
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
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();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
//D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
运行结果:
=======自己的加载器加载类调用方法=======
Сломайте механизм родительского делегирования Давайте возьмем еще один пример механизма безопасности песочницы, попробуем сломать механизм родительского делегирования и использовать пользовательский загрузчик классов для загрузки нашей собственной реализации java.lang.String.class.
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
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();
return data;
}
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();
}
}
/**
* 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
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) {
// 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.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
public static void main(String args[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
//尝试用自己改写类加载机制去加载自己写的java.lang.String.class
Class clazz = classLoader.loadClass("java.lang.String");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
运行结果:
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
Не закончено, см. следующую статью. Загрузчик классов JVM делает одну вещь 2
Из публичного аккаунта WeChat: Code Farmer Bennon