Истинный объем, Java-основа этого копателя настолько прочная | Исследование исходного кода загрузчика классов

задняя часть исходный код
Истинный объем, Java-основа этого копателя настолько прочная | Исследование исходного кода загрузчика классов

Эта статья участвовала в "Проект «Звезда раскопок»”, чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.

написать впереди

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

ClassLoader — это абстрактный объект-загрузчик классов, отвечающий за загрузку классов. Учитывая «двоичное имя» класса, загрузчик класса должен попытаться найти или сгенерировать часть данных, составляющих определенный класс. "Двоичное имя": любое имя класса предоставляется как строковый параметр метода ClassLoader. Имя класса в виде строки должно быть двоичным именем и определяется спецификацией языка Java следующим образом:

"java.lang.String"
"java.net.URLClassLoader$3$1"
"java.security.KeyStore$Builder$FileBuilder$1"

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

25979403-cf2c2873ecb24ecc.webp

25979403-343a2abf84fde107.webpОсновной класс лаунчера:

25979403-25ecd4393abf8147.webp

Класс запуска

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    //静态变量,初始化,会执行构造方法
    private static Launcher launcher = newLauncher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
        return launcher;
    }

    //构造方法执行
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //初始化扩展类加载器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Couldnotcreateextension class loader", var10);
        }
        try {
            //初始化应用类加载器
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //设置ContextClassLoader,设置为扩展类加载器
        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager) this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }
            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager:" + var2);
            }
            System.setSecurityManager(var3);
        }
    }

Здесь в конструкторе Launcher() выполняются четыре вещи:

Создайте загрузчик класса расширения

Создайте загрузчик класса приложения

Установить ContextClassLoader

Если вам нужно установить менеджер безопасности менеджер безопасности

Лаунчер статический, поэтому объект будет создан при инициализации, то есть будет запущен конструктор, поэтому при инициализации будут выполняться вышеописанные четыре шага. Продолжайте рассматривать ключевые этапы создания ExtClassLoader:

static class ExtClassLoader extends URLClassLoader {
    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
        final File[] var0 = getExtDirs();
        try {
            return (Launcher.ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                public Launcher.ExtClassLoader run() throws IOException {
                    int var1 = var0.length;
                    for (int var2 = 0; var2 < var1; ++var2) {
                        MetaIndex.registerDirectory(var0[var2]);
                    }
                    return new Launcher.ExtClassLoader(var0);
                }
            });
        } catch (PrivilegedActionException var2) {
            throw (IOException) var2.getException();
        }
    }

    void addExtURL(URL var1) {
        super.addURL(var1);
    }

    public ExtClassLoader(File[] var1) throws IOException {
        super(getExtURLs(var1), (ClassLoader) null, Launcher.factory);
        SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }

    private static File[] getExtDirs() {
        String var0 = System.getProperty("java.ext.dirs");
        File[] var1;
        if (var0 != null) {
            StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
            int var3 = var2.countTokens();
            var1 = new File[var3];
            for (int var4 = 0; var4 < var3; ++var4) {
                var1[var4] = new File(var2.nextToken());
            }
        } else {
            var1 = new File[0];
        }
        return var1;
    }
}

Также есть ключевые этапы создания AppClassLoader:

    //var1 类全名 * var2 是否连接该类
    public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
        int var3 = var1.lastIndexOf(46);
        if (var3 != -1) {
            SecurityManager var4 = System.getSecurityManager();
            if (var4 != null) {
                var4.checkPackageAccess(var1.substring(0, var3));
            }
        }
        //通常是false,想要返回TRUE可能需要设置启动参数 lookupCacheEnabled 为true。
        //为true时,具体的逻辑是C++写的
        if (this.ucp.knownToNotExist(var1)) {
            //如果这个类已经被这个类加载器加载,则返回这个 类,否则返回Null
            Class var5 = this.findLoadedClass(var1);
            if (var5 != null) {
                if (var2) {
                    //如果该类没有被连接,则连接,否则什么都不做
                    this.resolveClass(var5);
                }
                return var5;
            } else {
                throw new ClassNotFoundException(var1);
            }
        } else {
            return super.loadClass(var1, var2);
        }
    }

Анализ исходного кода ClassLoader

Класс ClassLoader, который является абстрактным классом, все последующие загрузчики классов наследуются от ClassLoader (за исключением запускаемого загрузчика классов), вот некоторые важные методы в ClassLoader.

loadClass

Этот метод загружает двоичный тип указанного имени (включая имя пакета). Этот метод больше не рекомендуется переписывать пользователям после JDK1.2, но пользователи могут вызывать этот метод напрямую. Метод loadClass() реализован ClassLoader сам класс. Логика является реализацией родительского режима делегирования. Исходный код выглядит следующим образом. loadClass (строковое имя, логическое разрешение) — это перегруженный метод. Параметр разрешения указывает, следует ли генерировать объект класса и выполнять операции, связанные с синтаксическим анализом:

Давайте снова поговорим о процессе загрузки класса:

1. Затем приложение сначала проверит, был ли загружен A, и если да, то вернется напрямую;

2. Если нет, перейдите к ext, чтобы проверить, был ли загружен A, если да, вернитесь напрямую;

3. Если нет, перейдите в boot, чтобы проверить, был ли загружен A, если да, вернитесь напрямую;

4. Если нет, то загружается boot.Если класс с указанным именем находится в E:\Java\jdk1.8\jre\lib*.jar, он будет загружен и завершен;

5. Если не найдено, загрузка не удалась;

6. начинается загрузка ext.Если класс с указанным именем найден в E:\Java\jdk1.6\jre\lib\ext*.jar, он будет загружен и завершен;

7. Если он не найден, загрузка ext не удалась;

8. Приложение загружено, если класс с указанным именем найден в пути к классам, он будет загружен и завершен;

9. Если не найдено, сбросить исключение ClassNotFoundException

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 先从缓存查找该class对象,找到就不用重新加载
            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
                    // 如果都没有找到,则通过自定义实现的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;
        }
    }

Загружает классы с указанным двоичным именем Реализация этого метода по умолчанию ищет классы в следующем порядке: Вызовите метод findLoadedClass(String), чтобы проверить, был ли загружен этот класс.

Используйте родительский загрузчик для вызова метода loadClass(String).Если родительский загрузчик имеет значение Null, загрузчик классов загружает встроенный загрузчик виртуальной машины.

Используйте метод findClass(String) для загрузки класса.Если соответствующий класс успешно найден в соответствии с вышеуказанными шагами, и значение параметра разрешения, полученного методом, равно true, то для обработки вызывается метод resolveClass(Class) класс.

defineClass

Метод defineClass() используется для анализа потока байтов в объект класса, который может распознать JVM.Этот метод может не только создавать экземпляр объекта класса через файл класса, но также создавать экземпляр объекта класса другими способами. Метод defineClass() обычно используется вместе с методом findClass(). В общем, при настройке загрузчика классов метод findClass() загрузчика классов будет напрямую перезаписан, а правила загрузки будут записаны. После получения байт-кода загружаемый класс, он преобразуется в поток, а затем вызывается метод defineClass() для создания объекта класса класса.Простой пример выглядит следующим образом:

 protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 获取类的字节数组
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            //使用defineClass生成class对象
            return defineClass(name, classData, 0, classData.length);
        }
    }

Однако, если вы напрямую вызываете метод defineClass() для создания объекта Class класса, объект Class этого класса не был разрешен (он все еще находится в фазе связывания, и связь должна быть разрешена), а его операция синтаксического анализа должна дождаться фазы инициализации.

findClass(String)

Как видно из предыдущего анализа, метод findClass() вызывается в методе loadClass().Когда родительский загрузчик не загружается в методе loadClass(), он вызывает свой собственный метод findClass() для завершения класса loading, так что гарантируется, что пользовательские загрузчики классов также соответствуют родительскому шаблону делегирования. Следует отметить, что специфическая логика кода метода findClass() в классе ClassLoader не реализована, вместо этого выбрасывается исключение ClassNotFoundException, в то же время следует знать, что метод findClass обычно является исходным кодом метод findClass() в классе ClassLoader, используемый с методом defineClass, следующим образом:

//直接抛出异常 
protected Class<?> findClass(String name) throws ClassNotFoundException { 
                throw new ClassNotFoundException(name); 
}

resolveClass

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

Суммировать

Я читал много статей о загрузчиках классов, написанных большими парнями, но я обобщил их и написал сам, и польза от чтения исходного кода реальна.

Инструкции по розыгрышу

1. Это событие официально поддерживается Nuggets, подробности можно посмотретьnuggets.capable/post/701221…

2. Вы можете участвовать, комментируя содержание, связанное со статьей, и это должно быть связано с содержанием статьи!

3. Все статьи этого месяца будут участвовать в лотерее, и все желающие могут взаимодействовать еще больше!

4. В дополнение к официальной лотерее Наггетс, я также отправлю периферийные подарки (кружка и несколько значков Наггетс, кружка будет передана внимательным комментаторам, значки будут выбраны случайным образом, и количество будет увеличиваться в зависимости от количества комментариев).