Загрузчик классов Java — принцип и применение загрузчика классов

Java
Загрузчик классов Java — принцип и применение загрузчика классов

что такое загрузчик классов

Classloader, как следует из названия, загружает классы. Виртуальная машина загружает данные, описывающие класс, из файла байт-кода класса в память, проверяет, преобразует, анализирует и инициализирует данные и, наконец, формирует тип Java, который может быть непосредственно использован виртуальной машиной. Механизм виртуальной машины. Понимание механизма загрузки классов в java может быстро решать различные проблемы с загрузкой во время выполнения и быстро находить основные причины, стоящие за ними, а также является мощным инструментом для решения трудноизлечимых заболеваний. Поэтому также очень важно усвоить принцип загрузки классов.

Процесс загрузки classloader

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

Весь процесс загрузки classloader по-прежнему очень сложен.Подробности см. в разделе «Углубленное понимание виртуальной машины Java». Для облегчения памяти можно одним предложением выразить весь процесс загрузки, «блюда в западном стиле готовятся к семейному банкету», то есть семейный (загрузочный) банкет (проверка) готовит (готовит) западный (разбор) тип (инициализация) блюда. Убедитесь, что вы сможете быстро запомнить его в будущем.

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

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

Механизм родительского делегирования загрузчика классов относится к вопросу о том, какой загрузчик должен загружать класс, когда между несколькими загрузчиками классов существует отношение родитель-потомок. Конкретный процесс выглядит следующим образом: когда класс загружен, он не будет загружен первым, а будет делегирован для загрузки своему собственному родительскому классу, а родительский класс будет делегирован своему собственному родительскому классу. Поэтому вся загрузка класса будет делегирована для загрузки родительскому классу верхнего уровня, Bootstrap Classloader, и тогда сам родительский класс не сможет выполнить запрос на загрузку, и дочерний загрузчик попытается загрузить его сам. Используя модель родительского делегирования, классы Java имеют иерархическую связь с приоритетом вместе с их загрузчиками. Благодаря этой иерархической модели можно избежать повторной загрузки классов, а также можно избежать основных классов с помощью разных загрузчиков классов. Загрузка в память создает конфликты и путаницу. , тем самым сохраняя основную библиотеку Java в безопасности.

Иерархия загрузки классов всей виртуальной машины Java показана на рисунке выше.Загрузчик классов Bootstrap отвечает за загрузку библиотеки классов, распознаваемой виртуальной машиной, из каталога /lib в память виртуальной машины. Обычно мы используем базовые библиотеки, такие как java.util., Java.IO., java.lang.** и т. д. загружаются корневым загрузчиком.

Загрузчик классов расширений отвечает за загрузку классов расширений JVM, таких как серия Swing, встроенный механизм js, синтаксический анализатор xml и т. д. Эти библиотеки классов начинаются с javax, а их пакеты jar расположены в каталоге /lib/ext. .

Загрузчик приложений (Application Classloader) также называется системным загрузчиком классов, который отвечает за загрузку библиотеки классов, указанной в пользовательском пути (ClassPath). Код, который мы пишем, и сторонние пакеты jar, которые мы используем, загружаются им.

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

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

Сценарии применения загрузчика классов

Загрузчик классов — это инновация языка java, а также важная причина популярности языка java. Гибко определяя механизм загрузки загрузчика классов, мы можем выполнять многие задачи, например разрешать конфликты классов, реализовывать горячую загрузку и горячее развертывание и даже шифровать пакеты jar. Далее мы представим эти специальные сценарии один за другим.

конфликт зависимостей

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

Столкнется ли Али с подобными проблемами при нормальном развитии проекта? Например, в Alibaba есть много зрелых промежуточных программ, которые отвечают за разные группы промежуточного программного обеспечения. Итак, когда в проекте используется другое промежуточное ПО, как избежать проблемы конфликта зависимостей? Во-первых, мы используем очень простой сценарий, чтобы описать, почему возникают конфликты классов.

Бизнес ссылается на промежуточное ПО для сообщений (например, metaq) и промежуточное ПО для микросервисов (например, dubbo), эти два промежуточных ПО также ссылаются на версии fastjson-2.0 и fastjson-3.0, а сам бизнес также ссылается на версию fastjson-1.0. Отличие этих трех версий в том, что количество методов в классе А разное, согласно механизму обработки maven-зависимостей, в качестве конечной зависимости приложения фактически будет использоваться fastjson-1.0 с кратчайшим путем ссылок, а два других версии fastjson будут игнорироваться. , тогда, когда промежуточное ПО вызовет метод method2(), оно выдаст исключение «метод не найден». Может быть, вы скажете, что обновление всех версий, зависящих от fastjson, до 3.0 решит проблему? Да, это может решить проблему, но в реальной работе это нереально.Во-первых, команда промежуточного программного обеспечения и бизнес-команда не являются командой, и они не могут добиться эффективной координации.Во-вторых, стабильность промежуточного программного обеспечения должна быть обеспечена. гарантировано. Версия может быть обновлена ​​из-за конфликтов пакетов. Более того, могут быть сотни пакетов, от которых зависит промежуточное программное обеспечение. Если это решается исключительно обновлением пакетов, трудно гарантировать не только стабильность, но и время, которое требуется расставлять посылки может быть удушающе.

Итак, как решить проблему конфликта пакетов? Ответ пандора.Настраивая загрузчик классов, для каждого промежуточного ПО настраивается загрузчик.Взаимоотношения между этими загрузчиками параллельны и не зависят друг от друга. Таким образом, загрузчик классов каждого промежуточного программного обеспечения может загружать свою собственную версию fastjson. Поскольку полное имя класса и загрузчик, который загружает класс, вместе образуют уникальный идентификатор класса в JVM, который также является основой для изоляции зависимостей в Alibaba Pandora.

Возможно, здесь у вас появятся новые сомнения.Согласно родительской модели делегирования, App Classloader наследует Custom Classloader соответственно.Затем, когда класс fastjson в бизнес-пакете загружается, он сначала будет делегирован Custom ClassLoader. Не приведет ли это к игнорированию версии fastjson, от которой она зависит? Это правда, так как же Пандора это сделала?

Прежде всего, когда ModuleClassLoader, соответствующий каждому промежуточному программному обеспечению, загружает соответствующий файл класса в середине, в соответствии с параметром export.index, настроенным промежуточным программным обеспечением, он отвечает за индексирование классов, которые необходимо выставить (в основном связанные классы, которые предоставляют интерфейсы) в exportedClassHashMap, а затем применить. Загрузчик классов программы будет хранить этот exportedClassHashMap, поэтому, когда код приложения загружает класс, он сначала определит, существует ли exportedClassHashMap в текущем классе, если он существует, он вернется напрямую, если если он не существует, используйте традиционный механизм родительского делегирования для загрузки класса. Таким образом, промежуточное программное обеспечение MoudleClassloader не только выполняет загрузку промежуточного программного обеспечения, но также реализует раскрытие ключевых классов обслуживания промежуточного программного обеспечения.

Мы можем примерно посмотреть на процесс загрузки класса приложения:

горячая загрузка

При разработке проекта нам нужно часто перезапускать приложение для отладки программы, но запуск java-проекта занимает всего несколько десятков секунд или несколько минут. Такая медленная скорость запуска сильно влияет на эффективность разработки программы, можно ли ее быстро запустить, а потом быстро разработать и проверить? Ответ тоже да, через classloader мы можем завершить загрузку измененного контента, а потом быстро начать.

Существует несколько часто используемых схем горячей загрузки, далее мы представим схему горячей загрузки, официально рекомендованную Spring, а именно spring boot devtools.

Прежде всего, нам нужно подумать, почему перезапуск приложения происходит медленнее, потому что при запуске приложения виртуальной машине JVM необходимо перезагрузить все приложения на всю виртуальную машину. Вполне возможно, что пакет jar, содержащийся в сложном приложении, может иметь сотни мегабайт, и каждое небольшое изменение загружается полностью, что, естественно, очень медленно. Итак, можем ли мы при изменении файла заменить соответствующую часть файла в JVM без его полной перезагрузки? И Spring Boot devtools основан на этой идее.

Как показано на рисунке выше, код проекта обычно состоит из четырех вышеуказанных частей, а именно базового класса, класса расширения, стороннего пакета/стороннего пакета и бизнес-кода, написанного нами. Строка выше — это наша обычная структура загрузки классов, где бизнес-код и сторонние/сторонние пакеты загружаются загрузчиком приложения. В фактическом процессе разработки и отладки основным изменением является бизнес-код, и бизнес-код будет меньше, чем содержимое стороннего пакета/стороннего пакета. Поэтому мы можем загрузить бизнес-код через пользовательский загрузчик Custom Classloader.Когда мониторинг обнаруживает, что бизнес-код изменился, мы перезагружаем и запускаем его, а связанные классы старого бизнес-кода собираются механизмом сборки мусора виртуальная машина.Автоматическая утилизация. Технологический процесс примерно следующий. Заинтересованные студенты могут посмотреть исходный код, так будет понятнее.

RestartClassLoader — это настраиваемый загрузчик классов, ядром которого является метод загрузки loadClass. Мы обнаружили, что, изменив механизм родительского делегирования, приоритетом по умолчанию является загрузка из самого себя. Если он не загружен сам по себе, он будет загружен из родитель. Это гарантирует, что бизнес-код может быть предпочтительно загружен с помощью RestartClassLoader. Затем перезагрузка кода приложения может быть завершена путем перезагрузки RestartClassLoader.

горячее развертывание

Суть горячего развертывания мало чем отличается от горячей загрузки.Обычно мы говорим, что горячее развертывание относится к загрузке загрузчиков классов в среде разработки, а горячее развертывание относится к использованию механизма загрузки загрузчиков классов в онлайн-среде для завершения развертывание бизнеса. . Таким образом, нет никакой существенной разницы в методах, используемых этими двумя. В дополнение к более быстрому выпуску «горячего» развертывания и «горячей» перезагрузки существует больше преимуществ более тонкой детализации выпуска. Мы можем представить следующий бизнес-сценарий.

Предположим, что платформа доставки маркетинговых материалов включает в себя разработку 4 бизнес-партнеров и должна обеспечивать бизнес в местах проведения мероприятий. И все коды этих четырех бизнес-партнеров в одном приложении. Следовательно, если бизнес-сторона вносит изменения в код, необходимо выпустить все приложение, и другие бизнес-стороны также должны последовать этому примеру. Поэтому каждый крошечный запуск требует полного релиза всего приложения. Не говоря уже об оценке риска стабильности, которую дает этот метод, можно представить себе эффективность всей итерации выпуска. Это, очевидно, неприемлемо в рамках концепции, согласно которой время и эффективность — это деньги во всем Интернете.

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

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

защита от шифрования

Как всем известно, пакет jar, созданный при разработке и компиляции Java, состоит из байт-кода .class, потому что формат файла байт-кода четко стандартизирован. Поэтому при декомпиляции байт-кода легко узнать его реализацию в исходном коде. Таким образом, обычно существуют следующие два требования. Например, на стороне сервера, когда мы предоставляем стороннюю реализацию пакета другим, мы не хотим, чтобы другие знали реализацию основного кода.Мы можем рассмотреть возможность шифрования пакета jar, который чаще встречается на стороне клиента, то есть , установочный пакет нашего упакованного apk, не хочу, чтобы другие декомпилировали его и перевернули с ног на голову другими, мы также можем зашифровать apk.

Суть шифрования пакета jar заключается в работе с файлами байт-кода. Однако спецификация загрузки классов в виртуальной машине JVM унифицирована, поэтому, когда мы, наконец, загрузим файл класса, нам все равно нужно выполнить спецификацию формата файла класса, иначе виртуальная машина не сможет нормально загрузиться. Следовательно, мы можем выполнить операцию прямого шифрования над классом при упаковке, а затем выполнить операцию обратного дешифрования через пользовательский загрузчик классов перед загрузкой файла класса, а затем загрузить его в соответствии со стандартным файлом класса, который завершен. файл загружается нормально. Таким образом, этот зашифрованный пакет jar может быть нормально загружен только загрузчиком классов, который может реализовать метод расшифровки.

Мы можем вставить простую схему реализации:

Таким образом, безопасность всего пакета jar в определенной степени повышается.Что касается более высокой безопасности, это зависит от безопасности алгоритма шифрования и от того, как гарантировать, что ключ алгоритма шифрования не будет утечек. Это похоже на матрешку, так называемая безопасность в основном относительна. И эти методы не являются абсолютными, например, расшифрованный файл класса можно сохранить, интерполируя classloader, кроме того, большинство JVM сами по себе не безопасны, вы также можете модифицировать JVM, чтобы получить расшифрованный код извне ClassLoader и сохранить его Сохранить на диск, тем самым минуя всю работу, проделанную вышеописанным шифрованием, конечно, стоимость этих операций гораздо выше, чем декомпиляция в чистом виде. Следовательно, гарантия безопасности безопасна до тех пор, пока стоимость взлома другой стороной выше, чем прибыль, поэтому определенной степени безопасности достаточно, чтобы уменьшить количество недорогих атак.

Суммировать

В этом документе представлен процесс загрузки и принцип загрузки загрузчика классов, а также представлены соответствующие сценарии использования в сочетании с характеристиками механизма загрузки классов. Из-за нехватки места конкретные детали реализации каждого сценария не представлены, а представлены только основные идеи реализации. Может быть, вы думаете, что применение classloader немного сложно, но на самом деле, пока вы понимаете, откуда загружать класс и понимаете механизм loadClass, вы уже преуспели более чем наполовину. Как говорится, все изменения неотделимы от оригинала, и если уловить суть, то легко решатся и другие проблемы.