Загрузчик классов Java и механизм родительского делегирования

Java

авторСБ Валар
Если вам нужно перепечатать, пожалуйста, сохраните оригинальную ссылку

Исходный код Java, использованный в этой статье,Java8Версия

Некоторые фотографии взяты с Baidu, если есть какие-либо нарушения, пожалуйста, свяжитесь с нами, чтобы удалить

содержание:

  • загрузчик классов
  • класс java.lang.ClassLoader
    • URLClassLoader и SecureClassLoader
    • ClassLoader Общий метод Исходный код Анализ
  • Механизм родительского делегирования
    • диаграмма
    • Анализ исходного кода
  • Анализ общих проблем

Предисловие: когда мы впервые столкнулись с Java, все, что мы писали в IDE (интегрированной среде разработки) или текстовом редакторе, были файлами .java, а после компиляции были сгенерированы файлы .class, также известные как файлы байт-кода.

javac HelloWorld.java   --->  HelloWorld.class

Для файла .class его необходимо загрузить в виртуальную машину, прежде чем его можно будет использовать.загрузка класса. Если вы хотите знать, как загружаются классы, вам нужно знатьзагрузчик классовиМеханизм родительского делегированияКонцепция чего-либо. Это то, что мы собираемся представить в этой статье.

1. Загрузчик классов

Тип погрузчик в Java можно разделить на два типа:

  • Загрузчик системных классов
  • пользовательский загрузчик классов

Существует три загрузчика системных классов:

  • Bootstrap ClassLoader: запуск загрузчика классов
  • Расширения CloseLoader: Расширенный класс загрузчик
  • App ClassLoader: также известный как SystemAppClass, загрузчик системных классов.

1.1 Bootstrap ClassLoader

Bootstrap ClassLoader используется для загрузкиJVM(Java虚拟机)Необходим для выполнения системных классов, используяc++выполнить.

Загрузите классы из следующих путей:

  1. %JAVA_HOME%/jre/libКаталоги, такие как rt.jar, resources.jar, charsets.jar и т. д.
  2. Вы можете указать параметр -XbootclassPath при запуске JVM, чтобы изменить каталог загрузки Bootstrap ClassLoader.

Запуск виртуальной машины Java выполняется путем создания начального класса с помощью Bootstrap ClassLoader. Каталог, загруженный Bootstrap ClassLoader, можно получить с помощью следующего кода:

public class ClassLoaderTest {
    public static void main(String[]args) {
        System.out.println(System.getProperty("sun.boot.class.path"));
    }
}

Результат печати:

C:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_102\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_102\jre\classes

Можно найти, что почти все$JAVA_HOME/jre/libПакеты jar в каталоге, включая rt.jar, resources.jar и charsets.jar и т. д.

1.2 Extensions ClassLoader

Extensions ClassLoader (загрузчик класса расширения) определяетсяExtClassLoaderкласс реализован,ExtClassLoaderКласс находится вsun.misc.Launcherclass, является его статическим внутренним классом. заLauncherКласс можно рассматривать как точку входа виртуальной машины Java.

ExtClassLoaderЧасть кода выглядит следующим образом:

Расширения ClassLoader отвечает за преобразованиеJAVA_HOME/jre/lib/extили по системной переменной-Djava.ext.dirБиблиотека классов в указанном месте загружается в память.

Каталог загрузки Extensions ClassLoader можно получить с помощью следующего кода:

System.out.println(System.getProperty("java.ext.dirs"));

Результат печати:

C:\Program Files\Java\jdk1.8.0_102\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext

1.3 App ClassLoader

Также упоминается SystemAppClass (загрузчик системных классов), в частностиAppClassLoaderкласс реализован,AppClassLoaderКласс также находится в г.sun.misc.Launcherв классе.

Часть кода выглядит следующим образом:

  1. В основном он загружает все jar-файлы и файлы классов в каталог Classpath и является загрузчиком классов по умолчанию в программе. Classpath здесь относится к каталогу bin нашего Java-проекта.
  2. Вы также можете загружать файлы jar и class в каталог, указанный параметром -Djava.class.path.

Каталог загрузки App ClassLoader можно получить с помощью следующего кода:

System.out.println(System.getProperty("java.class.path"));

Результат печати:

C:\workspace\Demo\bin

Этот путь на самом деле является текущим каталогом проекта Java, в котором хранятся скомпилированные файлы классов.


В Java, в дополнение к загрузчику классов, предоставляемому тремя вышеупомянутыми системами, вы также можете настроить загрузчик классов.

1.4. Пользовательские загрузчики классов

Чтобы загружать пакеты jar или файлы классов из указанного каталога, мы можем реализовать собственный загрузчик классов, унаследовав класс java.lang.ClassLoader.

При настройке загрузчика классов мы обычно перезаписываемfindClassметод, а вfindClassВызов методаdefineClassметод.

Далее я сначала представлю конкретное содержимое, связанное с классом ClassLoader, а затем рассмотрю демонстрацию пользовательского загрузчика классов.

2 класс java.lang.classloader

2.1 Связь между ClassLoader, URLClassLoader и SecureClassLoader

Из приведенного выше на ExtClassLoader, исходной карте AppClassLoader мы видим, что они унаследованы от URLClassLoader, URLClassLoader Что это такое, что за этим стоит?

Сначала кважные отношения наследованиярисунок:

  • ClassLoader — это абстрактный класс, расположенный в пакете java.lang, который определяет основные функции ClassLoader.
  • SecureClassLoader наследует абстрактный класс ClassLoader, но SecureClassLoader не является классом реализации ClassLoader, а расширяет класс ClassLoader, добавляя функции разрешений, что усиливает безопасность ClassLoader.
  • URLClassLoader наследуется от SecureClassLoader и используется для загрузки классов и ресурсов из JAR-файлов и папок по URL-путям.
  • И ExtClassLoader, и AppClassLoader наследуются от URLClassLoader. Оба они являются внутренними классами Launcher. Launcher — это приложение входа виртуальной машины Java. И ExtClassLoader, и AppClassLoader инициализируются в Launcher.

2.2 Отношения между обычными классами, AppClassLoader и ExtClassLoader

связь:

  • Загрузите обычный класс (здесь указывая, что класс кода мы написали, который мы написали, класс теста в демонстрации ниже) Загрузчик является AppClassLoader, родительский носитель AppClassLoader является ExtClassLoader.
  • А родительским загрузчиком ExtClassLoader является Bottstrap ClassLoader.

Есть еще 2 вывода:

  • У каждого класса есть загрузчик классов
  • У каждого загрузчика классов есть родительский загрузчик

Готовим простую демку Файл self Test.java.

public class Test{}
public class Main {
    public static void main(String[] args) {
		ClassLoader cl = Test.class.getClassLoader();
		System.out.println("ClassLoader is:"+cl.toString());
	}
}

Чтобы вы могли добраться до файла Test.class загрузчика классов, затем распечатайте его. оказаться:

sun.misc.Launcher$AppClassLoader@75b83e92

То есть файл test.class загружается AppClassLoader.

Кто загружает AppClassLoader? На самом деле у AppClassLoader тоже есть родительский загрузчик, его мы можем получить через следующий код

public class Test {
    public static void main(String[] args) {
        ClassLoader loader = Test.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader);
            loader = loader.getParent();
        }
    }
}

Результат приведенного выше кода выглядит следующим образом:

sun.misc.Launcher$AppClassLoader@7565783b
sun.misc.Launcher$ExtClassLoader@1b586d23
  • Загрузчик класса, который загружает Test, — это AppClassLoader, а родительский загрузчик AppClassLoader — ExtClassLoader.
  • А родительским загрузчиком ExtClassLoader является Bottstrap ClassLoader.

Что касается загрузки родительского загрузчика родительского загрузчика в ExtClassLoader, это потому, что Bootstrap ClassLoader написан C ++, а не класс Java, поэтому мы не можем получить его ссылку в коде Java.

2.3 Общий метод класса java.lang.ClassLoader

В предыдущем разделе мы видели, что ClassLoadergetParentметод,getParentТо, что вы получаете, на самом деле является его родительским загрузчиком. В этом разделе будут представлены некоторые важные методы ClassLoader через исходный код.

getParent()
ClassLoader类
---------
public final ClassLoader getParent() {
    if (parent == null) return null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(parent, Reflection.getCallerClass());
    }
    return parent;
}

Мы видим, что есть две возможности для возвращаемого значения:илиparentПеременная.

Из исходного кода также можно узнать, что это финальный модифицированный метод.Мы знаем, что финальная модификация указывает на то, что функция, предоставляемая этим методом, соответствует текущим требованиям и не может быть переписана. Таким образом, каждый из его подклассов вызываетgetParent()В конечном итоге методы будут обрабатываться ClassLoader.

Что такое родительская переменная? Когда мы смотрим на исходный код, мы можем обнаружить, что назначение родителя находится в конструкторе.

ClassLoader类
---------
private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    ... //省略了无关代码
}

И этот конструктор является закрытым и не может быть вызван извне, поэтому его вызывающий объект по-прежнему остается внутренним. Затем я нашел два других метода построения.

ClassLoader类
---------
protected ClassLoader() {
    this(checkCreateClassLoader(), getSystemClassLoader());
}
    
protected ClassLoader(ClassLoader parent) {
    this(checkCreateClassLoader(), parent);
}

так:

  1. Вы можете указать родителя при вызове конструктора ClassLoder.
  2. Если не указано, будет использоватьсяgetSystemClassLoader()Возвращаемое значение метода.

Тогда посмотрите на вышеупомянутый исходный код GetSystemclasslassLoader:

ClassLoader类
---------
public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

Он возвращает scl. существуетinitSystemClassLoader()Присвоение переменной scl находится в методе.

ClassLoader类
---------
private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); //1
        if (l != null) {
            Throwable oops = null;
            scl = l.getClassLoader();
            ...//省略代码
        }
        sclSet = true;
    }
}

Дело в том, что в Note 1 он получаетLauncherКласс объекта, а затем позвонитьLauncherКатегорияgetClassLoader()метод.

Launcher类
---------
public ClassLoader getClassLoader() {
    return this.loader;
}

那这个this.loader是什么呢? существуетLauncherнаходится в классе, его операция присваивания находится вLauncherв конструкторе его значение равноLauncherв классеAppClassLoader:

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);
    }
    ...
}

Вот и вся загадка раскрыта:

При создании ClassLoder,

  1. Вы можете указать ClassLoder в качестве его родителя, который является его родительским загрузчиком.
  2. Если не указано, будет использоватьсяgetSystemClassLoader()Возвращаемое значение метода (т.Launcherв классеAppClassLoader) в качестве его родителя.
  3. Родительский загрузчик можно получить с помощью метода getParent().
defineClass()

Может преобразовывать двоичный контент класса в объект класса, если он не соответствует требованиям, например, будет выдано исключениеClassFormatError,NoClassDefFoundError.

При настройке ClassLoader мы обычно сначала считываем определенный файл в объект byte[], а затем используем этот метод для преобразования его в объект класса.

ClassLoader类
---------
/**
* String name:表示预期的二进制文件名称,不知道的话,可以填null。
* byte[] b:此class文件的二进制数据
* int off:class二进制数据开始的位置
* int len:class二进制数据的总长度
*/

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError
{
    return defineClass(name, b, off, len, null);
}


protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}
findClass()

findClass()метод, как правило,loadClass()Вызов метода для загрузки класса с указанным именем.

ClassLoader类
---------
/**
* String name:class文件的名称
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
} 

Из исходного кода мы видим, что в классе ClassLoader нет определенной логики, но ожидание реализации его подклассов Благодаря приведенному выше анализу мы знаем, что два загрузчика системных классовExtClassLoaderиAppClassLoaderунаследовано отURLClassLoaderДавайте взглянемURLClassLoaderконкретный код в .

URLClassLoader类
---------
protected Class<?> findClass(final String name) throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                        ...
    return result;
}

private Class<?> defineClass(String name, Resource res) throws IOException {
    ...
    URL url = res.getCodeSourceURL();
    ...
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        ...
        return defineClass(name, bb, cs);
    } else {
        byte[] b = res.getBytes();
        ...
        return defineClass(name, b, 0, b.length, cs);
    }
}

Вы можете видеть, что после обработки входящего имени он вызываетdefineClass(name, res); В этом методе файлы в соответствующем формате загружаются в основном через ресурсы res и URL-адреса, и, наконец, через ClassLoaderdefineClassМетод загружает конкретный класс.

loadClass()

упоминалось в предыдущем разделеfindClass()обычно вloadClass()позвони, чтоloadClass()Что тогда? фактическиloadClass()этоМеханизм родительского делегированияКонкретная реализация , поэтому после того, как мы представим механизм родительского делегирования, мы проанализируем его позже.loadClass().

3 Введение в механизм родительского делегирования

3.1 Схематическое представление механизма родительского делегирования

Во-первых, краткое введение в механизм родительской делегирования: Классический погрузчик использует режим родительской делегирования, чтобы найти класс (то есть при загрузкеClass). Так называемая родительская делегирование является

  1. Сначала определите, был ли загружен класс
  2. Если нет, он не ищется сам по себе, а делегируется родительскому загрузчику для поиска, который, в свою очередь, повторяется, пока не будет делегирован самому верхнему Bootstrap ClassLoader.
  3. Если Bootstrap ClassLoader найдет класс, он вернется напрямую
  4. Если не найден, то продолжайте отказываться от взгляда, если вы не нашли, последнее будет относиться к себе, чтобы найти

(Изображение взято с http://liuwangshu.cn/application/classloader/1-java-classloader-.html)

  • Красная стрелка указывает направление восходящего делегирования.Если текущий загрузчик класса не находит объект класса в кэше, он запросит работу родительского загрузчика. до того какBootstrap ClassLoader.
  • Черная стрелка указывает направление поиска, еслиBootstrap ClassLoaderДоступна с%JAVA_HOME%/jre/libЕсли каталог или указанный каталог -Xbootclasspath найден, объект будет возвращен напрямую, в противном случае он будетExtClassLoaderНайти.
  • ExtClassLoaderВоляJAVA_HOME/jre/lib/extили-Djava.ext.dirНайдите его в указанном месте, если не можете найти, отдайтеAppClassLoader,AppClassLoaderПросто выполните поиск в каталоге bin текущего проекта.
  • Если вы все еще не можете найти его, мы настроим егоCustomClassLoaderПоиск, конкретные результаты поиска, зависит от того, как мы реализуем пользовательский классfindClassметод.

3.2 Механизм родительского делегирования анализа исходного кода

Далее посмотрим, как механизм родительского делегирования отражен в исходном коде. Сначала посмотрите на исходный код loadClass:

ClassLoader类
---------
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先,根据name检查类是否已经加载,若已加载,会直接返回
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //若当前类加载器有父加载器,则调用其父加载器的loadClass()
                    c = parent.loadClass(name, false);
                } else {
                    //若当前类加载器的parent为空,则调用findBootstrapClassOrNull()
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }
        
            if (c == null) {
                // 1.如果到这里c依然为空的话,表示一直到最顶层的父加载器也没有找到已加载的c,那就会调用findClass进行查找
                // 2.在findClass的过程中,如果指定目录下没有,就会抛出异常ClassNotFoundException
                // 3.抛出异常后,此层调用结束,接着其子加载器继续进行findClass操作
                long t1 = System.nanoTime();
                c = findClass(name);

                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

findBootstrapClassOrNull(): вы можете видеть, что после проверки имени он, наконец, вызывает методnativeметодfindBootstrapClass(). существуетfindBootstrapClass()В конечном итоге метод будет использовать загрузчик классов Bootstrap для поиска класса.

ClassLoader类
---------
private Class<?> findBootstrapClassOrNull(String name)
{
    if (!checkName(name)) return null;
    return findBootstrapClass(name);
}
    
private native Class<?> findBootstrapClass(String name);

4 часто задаваемых вопроса

4.1 Зачем использовать родительский механизм делегирования?

  1. Избегайте повторной загрузки.Если класс был загружен один раз, нет необходимости загружать его снова, а сначала прочитать непосредственно из кеша.
  2. Из соображений безопасности, если вы не используете родительский режим делегирования, вы можете настроить класс String для замены системного класса String, что вызовет риски безопасности.Использование родительского режима делегирования сделает системный класс String при запуске виртуальной машины Java После загрузки невозможно настроить класс String для замены системного класса String.

4.2 Отличается от классов погрузчика нагрузки, класс будет такой же JVM, что и класс?

Не буду. 在Java中,我们用包名+类名作为一个类的标识。 但在JVM中,一个类用其имя пакета + классНаваэкземпляр ClassLoaderВ качестве уникального идентификатора классы, загруженные разными загрузчиками классов, будут помещены в разные пространства имен.

Глядя на демо,

  1. Используйте два пользовательских загрузчика классов для загрузки пользовательского класса
  2. Затем полученный класс оценивается java.lang.Object.equals(…).
public class Main {
    public static void main(String[] args) {
    
        ClassLoaderTest myClassLoader = new ClassLoaderTest("F:\\");
        ClassLoaderTest myClassLoader2 = new ClassLoaderTest("F:\\");
        try {
            Class c = myClassLoader.loadClass("com.example.Hello");
            Class c2 = myClassLoader.loadClass("com.example.Hello");

            Class c3 = myClassLoader2.loadClass("com.example.Hello");

            System.out.println(c.equals(c2)); //true
            System.out.println(c.equals(c3)); //flase
    }
}

Выходной результат:

true
false

Только два класса с одинаковым именем класса, загруженные одним и тем же загрузчиком классов, будут считаться виртуальным компьютером Java одним и тем же классом.

Приведенная выше демонстрация используется в пользовательском ClassLoader:

自定义的类加载器
注意点:
1.覆写findClass方法
2.让其可以根据name从我们指定的path中加载文件,也就是将文件正确转为byte[]格式
3.使用defineClass方法将byte[]数据转为Class对象
-------------
public class ClassLoaderTest extends ClassLoader{
    private String path;
    public ClassLoaderTest(String path) {
        this.path = path;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        byte[] classData = classToBytes(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            clazz= defineClass(name, classData, 0, classData.length);
        }
        return clazz;
    }
    private byte[] classToBytes(String name) {
        String fileName = getFileName(name);
        File file = new File(path,fileName);
        InputStream in=null;
        ByteArrayOutputStream out=null;
        try {
            in = new FileInputStream(file);
            out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length=0;
            while ((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
            return out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(in!=null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try{
                if(out!=null) {
                    out.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return null;
    }
    private String getFileName(String name) {
        int index = name.lastIndexOf('.');
        if(index == -1){
            return name+".class";
        }else{
            return name.substring(index+1)+".class";
        }
    }
}

Эпилог

To this Java class loader and the dual-pro-entrustment mechanism have a probably, if there is a wrong place in the article, or there are other important contents about the class loader, welcome to leave a message in the comment area, communicate together учиться.

Следующий скажетJava новый процесс объекта, в котором будет участвовать классзагрузить, проверить, а при создании объектавыделение памяти в кучеи так далее.

Ссылаться на:Шесть Ван Шу.Можно/применение...

blog.CSDN.net/Чуть, чем Япония/искусство…

blog.csdn.net/just люблю тебя ...