Вы уверены, что действительно понимаете «родительское делегирование»? !

Java JVM

GitHub 19k Star Путь Java-инженера к тому, чтобы стать богом, не приходите и не узнайте об этом!

GitHub 19k Star Путь Java-инженера к тому, чтобы стать богом, разве вы не хотите узнать об этом!

Путь 19-тысячного Java-инженера GitHub к тому, чтобы стать богом, на самом деле не приходите, чтобы узнать об этом!

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

Помню, во время интервью несколько дней назад я болтал с кандидатом о механизме загрузки классов в JVM, он говорил о родительском делегировании и уверенно рассказал мне о своем понимании родительского делегирования.

Так как редко можно было встретить кандидата, знающего больше о блочном знании, мы начали противостояние «раундов 300», после того как мы задали эти вопросы, прошло около получаса.

Наконец, последователь сказал мне: "Никогда бы не подумал, что технический руководитель, проработавший 7 лет, на самом деле попал в руки родителей! ! !"

Во-первых, давайте рассмотрим, какие вопросы я ему задавал, и посмотрим, на сколько из них вы сможете ответить:

1. Что такое родительское делегирование?

2. Зачем нужно родительское делегирование, что плохого в том, чтобы не делегировать?

3. Наследуются ли отношения между «родительским загрузчиком» и «дочерним загрузчиком»?

4. Как достигается родительское делегирование?

5. Могу ли я активно уничтожить этот механизм родительского делегирования? Как уничтожить?

6. Почему переопределение метода loadClass может разрушить родительское делегирование?В чем разница между этим методом и findClass() и defineClass()?

7. Расскажите мне об известном вам примере, когда родительское делегирование было нарушено.

8. Почему JNDI, JDBC и т. д. должны уничтожать родительское делегирование?

9. Почему TOMCAT уничтожает родительское делегирование?

10. Расскажите о своем понимании модульной технологии!

Выше, 10 вопросов, ответьте с самого начала, сколько вопросов вы можете придерживаться?

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

Во-первых, мы знаем, что виртуальная машина должна использовать загрузчик классов для загрузки класса в процессе загрузки, а в Java существует много загрузчиков классов, поэтому, когда JVM хочет загрузить файл .class, какой класс следует использовать?Как насчет загрузки загрузчика?

Это должно упомянуть "родительский механизм делегирования".

Прежде всего, нам нужно знать, что в языковой системе Java поддерживаются следующие 4 вида загрузчиков:

  • Bootstrap ClassLoader Bootstrap ClassLoader
  • Extention ClassLoader Стандартный загрузчик класса расширения
  • Приложение ClassLoader Приложение ClassLoader
  • User ClassLoader Пользовательский загрузчик классов

Между этими четырьмя загрузчиками классов существует иерархическая связь, как показано на следующем рисунке.

-w704

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

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

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

На самом деле, у четырех типов загрузчиков, предусмотренных в Java, есть свои обязанности:

  • Bootstrap ClassLoader в основном отвечает за загрузку основной библиотеки классов Java, rt.jar, resources.jar, charsets.jar и класса в %JRE_HOME%\lib.
  • Extention ClassLoader в основном отвечает за загрузку пакетов jar и файлов классов в каталог %JRE_HOME%\lib\ext.
  • Application ClassLoader , который в основном отвечает за загрузку всех классов в пути к классам текущего приложения.
  • User ClassLoader , определяемый пользователем загрузчик классов, который может загружать файл класса по указанному пути.

Другими словами, определяемый пользователем класс, такой как com.hollis.ClassHollis, в любом случае не загружается загрузчиками Bootstrap и Extention.

Зачем нужно родительское делегирование?

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

Или эта иерархическая связь является приоритетной.

Например, класс, определенный в пакете java.lang, поскольку он хранится в rt.jar, будет доверен загрузчику классов Bootstrap и, наконец, загружен загрузчиком классов Bootstrap, когда он агрегируется в процессе загрузки.

Определенный пользователем класс com.hollis.ClassHollis также будет делегирован Bootstrap ClassLoader, но поскольку Bootstrap ClassLoader не отвечает за загрузку класса, он будет загружен с помощью Extention ClassLoader, а Extention ClassLoader не отвечает за это. class., в конце концов, будет загружен Application ClassLoader.

Этот механизм имеет ряд преимуществ.

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

Кроме того,Безопасность также обеспечивается за счет родительского делегирования. Поскольку при загрузке Bootstrap ClassLoader он будет загружать только классы из jar-пакета в JAVA_HOME, такие как java.lang.Integer, то этот класс не будет заменяться по желанию, если только кто-то не запустится на вашу машину и не уничтожит ваш JDK.

Затем вы можете запретить кому-либо настраивать java.lang.Integer с неработающими функциями для загрузки. Это может эффективно предотвратить подделку основного Java API.

Есть ли связь между наследованием «родительско-дочерних загрузчиков»?

Многие люди видят такие имена, как родительский загрузчик и дочерний загрузчик, и думают, что между загрузчиками классов в Java существует отношение наследования.

В Интернете есть даже много статей с подобными заблуждениями.

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

Ниже приведено определение родительского загрузчика в ClassLoader:

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;
}

Как осуществляется родительское делегирование?

Модель родительского делегирования важна для обеспечения стабильной работы Java-программ, но ее реализация несложна.

Код, реализующий родительское делегирование, сконцентрирован в методе loadClass() java.lang.ClassLoader.:

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

Код не сложен для понимания, в основном следующие шаги:

1. Сначала проверьте, был ли загружен класс 2. Если он не загружен, вызовите метод loadClass() родительского загрузчика, чтобы загрузить его 3. Если родительский загрузчик пуст, загрузчик запускаемого класса будет использоваться как родительский загрузчик по умолчанию. 4. Если родительский класс не загружается, после создания исключения ClassNotFoundException вызовите собственный метод findClass() для его загрузки.

Как активно разрушать механизм родительского делегирования?

Зная реализацию модели родительского делегирования, очень просто сломать механизм родительского делегирования.

Поскольку его родительский процесс делегирования реализован в методе loadClass, тоЕсли вы хотите уничтожить этот механизм, вы можете настроить загрузчик классов и переписать в нем метод loadClass, чтобы он не выполнял родительское делегирование.

Разница между loadClass(), findClass(), defineClass()

В ClassLoader есть много методов, связанных с загрузкой классов, я упоминал loadClass ранее, кроме того, есть findClass и defineClass, так в чем разница между этими методами?

  • loadClass()
    • Это основной метод загрузки класса, и в этом методе реализован механизм родительского делегирования по умолчанию.
  • findClass()
    • Загрузить байт-код .class на основе имени или местоположения
  • definclass()
    • Преобразование байт-кода в класс

Здесь нам нужно расширить loadClass и findClass.Как мы уже говорили, когда мы хотим настроить загрузчик классов и когда мы нарушаем принцип родительского делегирования, мы перепишем метод loadClass.

Так что, если мы хотим определить загрузчик классов, но не хотим нарушать родительскую модель делегирования?

В настоящее время вы можете наследовать ClassLoader и переопределить метод findClass. Метод findClass() — это недавно добавленный метод ClassLoader после JDK1.2.

 /**
 * @since  1.2
 */
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

Этот метод просто генерирует исключение и не имеет реализации по умолчанию.

После JDK1.2 пользователям больше не рекомендуется напрямую переопределять метод loadClass(), а реализовывать собственную логику загрузки класса в методе findClass().

Потому что в логике метода loadClass(), если загрузчик родительского класса не загружается, он вызывает свой собственный метод findClass() для завершения загрузки.

Итак, если вы хотите определить свой собственный загрузчик классов и следовать модели родительского делегирования, вы можете наследовать от ClassLoader и реализовать свою собственную логику загрузки в findClass.

Примеры неработающего родительского делегирования

Механизм родительского делегирования нередко нарушается.Многие фреймворки, контейнеры и т. д. нарушают этот механизм для выполнения определенных функций.

Первая нарушенная ситуация — перед родительским делегированием.

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

Второй — это случай, когда JNDI, JDBC и т. д. необходимо загрузить класс реализации интерфейса SPI.

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

Четвертое — появление веб-контейнеров, таких как tomcat.

Пятое — применение модульных технологий, таких как OSGI и Jigsaw.

Почему JNDI, JDBC и т. д. должны нарушать родительское делегирование?

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

Однако помимо API существует еще и метод SPI.

Как и в типичном сервисе JDBC, мы обычно создаем соединения с базой данных следующими способами:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");

Прежде чем приведенный выше код будет выполнен, DriverManager сначала будет загружен загрузчиком классов, поскольку класс java.sql.DriverManager расположен в rt.jar, поэтому он будет загружен корневым загрузчиком.

При загрузке класса выполняются статические методы класса. Один из ключевых фрагментов кода:

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

Этот код попытается загрузить все классы реализации ниже пути к классам, которые реализуют интерфейс драйвера.

Затем возникает проблема.

DriverManager загружается корневым загрузчиком. Когда приведенный выше код встречается во время загрузки, он попытается загрузить все классы реализации драйвера, но эти классы реализации в основном предоставляются третьими сторонами. В соответствии с принципом родительского делегирования, сторонние классы не могут быть загружены корневым загрузчиком.

Итак, как решить эту проблему?

Итак, простоВ JDBC принцип родительского делегирования нарушается введением ThreadContextClassLoader (загрузчик контекста потока, по умолчанию AppClassLoader).

Мы копаемся в методе ServiceLoader.load, чтобы увидеть:

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

Первая строка получает загрузчик класса контекста потока AppClassLoader текущего потока, который используется для загрузки конкретного класса реализации в пути к классам.

Почему Tomcat нарушает родительское делегирование

Мы знаем, что Tomcat — это веб-контейнер, поэтому в веб-контейнере может потребоваться развертывание нескольких приложений.

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

Например, несколько приложений должны зависеть от hollis.jar, но приложение A должно зависеть от версии 1.0.0, а приложение B должно зависеть от версии 1.0.1. Один класс в обеих версиях — com.hollis.Test.class.

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

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

Механизм загрузки классов Tomcat: для достижения изоляции классы, определенные веб-приложением, загружаются первыми, поэтому собственный загрузчик классов каждого приложения, WebAppClassLoader, отвечает за загрузку файлов классов в свой собственный каталог без следования соглашению о родительском делегировании. будет передан CommonClassLoader для загрузки, когда он не рассчитан по времени, что прямо противоположно родительскому делегированию.

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

В последние годы технология модульности стала очень зрелой, и технология модульности была применена в JDK 9.

Фактически, еще до JDK 9 структура OSGI уже была модульной.Причина, по которой OSGI может обеспечивать горячую замену модулей и точный контроль внутренней видимости модулей, заключается в его специальном механизме загрузки классов.Взаимоотношения между загрузчиками больше не являются древовидной структурой родительской модели делегирования, а превратились в сложную сетевую структуру.

-w942

В JDK родительское делегирование больше не является абсолютным.

До JDK9 все основные классы JVM находились в пакете rt.jar, который также был краеугольным камнем работы JRE.

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

В JDK9 весь JDK строится на основе модуляризации.Предыдущие rt.jar и tool.jar были разбиты на десятки модулей.При компиляции компилировались только реально используемые модули, и у каждого класса загрузчик был свой Only load the modules за которые вы несете ответственность.

Class<?> c = findLoadedClass(cn);
if (c == null) {
    // 找到当前类属于哪个模块
    LoadedModule loadedModule = findLoadedModule(cn);
    if (loadedModule != null) {
        //获取当前模块的类加载器
        BuiltinClassLoader loader = loadedModule.loader();
        //进行类加载
        c = findClassInModuleOrNull(loadedModule, cn);
     } else {
          // 找不到模块信息才会进行双亲委派
            if (parent != null) {
              c = parent.loadClassOrNull(cn);
            }
      }
}

Суммировать

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

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

Прочитав эту статью, я написал в своем резюме: я знаком с механизмом загрузки классов в Java, и меня не убеждают спрашивать!

Об авторе:Hollis, человек с уникальным увлечением программированием, технический эксперт Alibaba, соавтор «Трех курсов для программистов» и автор серии статей «Дорога к Java-инженерам».

Если у вас есть комментарии, предложения или вы хотите пообщаться с автором, вы можете подписаться на официальный аккаунт [Hollis], оставьте мне сообщение прямо в фоновом режиме.