Глубокое понимание виртуальной машины Java (механизм загрузки классов)

Java JVM
Глубокое понимание виртуальной машины Java (механизм загрузки классов)

Статья была впервые опубликована в публичном аккаунте WeChat: BaronTalk

В предыдущей статье мы представили«Структура файла класса», в этой статье рассмотрим, как виртуальная машина загружает классы.

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

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

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

1. Время загрузки класса

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

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

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

  1. Обнаружены 4 инструкции байт-кода new, getstatic, putstatic или invokestatic;
  2. При использовании метода пакета java.lang.reflect для вызова отражения класса;
  3. При инициализации класса, когда обнаруживается, что его родительский класс не был инициализирован, ему необходимо сначала инициировать инициализацию своего родительского класса;
  4. При запуске виртуальной машины пользователю необходимо указать основной класс для выполнения, и виртуальная машина сначала инициализирует этот класс;
  5. При использовании поддержки динамического языка JDK 1.7, если последним результатом синтаксического анализа экземпляра java.lang.invoke.MethodHandle является дескриптор метода REF_getStatic, REF_putStatic и REF_invokeStatic, а класс, соответствующий этому дескриптору метода, не инициализирован.

«Есть и только» пять вышеперечисленных сценариев вызовут инициализацию класса, и поведение в этих пяти сценариях называется активной ссылкой на класс. В дополнение к этому все способы ссылки на класс, которые не запускают инициализацию, называются пассивными ссылками. Например, следующие сценарии являются пассивными ссылками:

  1. Обращение к статическим полям родительского класса через подкласс не приведет к инициализации подкласса;
  2. Ссылка на класс через определение массива не вызывает инициализацию этого класса;
  3. Константы будут храниться в пуле констант вызывающего класса на этапе компиляции, и, по сути, они не связаны напрямую с классом, определяющим константу, поэтому инициализация класса, определяющего константу, не будет инициирована;

2. Процесс загрузки класса

нагрузка

«Загрузка» здесь относится к этапу процесса «загрузки класса». На этапе загрузки виртуальная машина должна сделать 3 вещи:

  1. Получить двоичный поток байтов, определяющий этот класс по его полному имени;
  2. Преобразуйте статическую структуру хранения, представленную этим потоком байтов, в структуру данных времени выполнения области метода;
  3. Объект java.lang.Class, представляющий этот класс, создается в памяти как запись доступа к различным данным этого класса в области методов.

проверять

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

  1. проверка формата файла: на первом этапе проверяется, соответствует ли поток байтов спецификации формата файла класса и может ли он быть обработан текущей версией виртуальной машины. Точки проверки в основном включают: начинается ли он с магического числа 0xCAFEBABE; находятся ли основной и дополнительный номера версий в пределах диапазона обработки текущей виртуальной машины; имеются ли неподдерживаемые типы констант в константах пула констант; каждая ли часть Файл класса и сам файл Есть другая информация, которая была удалена или добавлена, и так далее.

  2. проверка метаданных: второй этап заключается в выполнении семантического анализа информации, описанной байт-кодом, чтобы убедиться, что описанная информация соответствует требованиям спецификации языка Java.Точки проверки на этом этапе включают: имеет ли этот класс родительский класс; является ли родительский класс класс этого класса наследует классы, которые не могут наследоваться; если этот класс не является абстрактным классом, реализует ли он все методы, необходимые для реализации в своем родительском классе или интерфейсе; конфликтуют ли поля и методы в классе с родительский класс и так далее.

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

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

Подготовить

Фаза подготовки — это фаза формального выделения памяти для переменных класса и установки начальных значений переменных класса, память, используемая этими переменными, будет выделена в области методов. На данном этапе необходимо выделить два запутанных понятия:

  • Прежде всего, в настоящее время в выделение памяти включаются только переменные класса (переменные, измененные статическими), а не переменные экземпляра.Переменные экземпляра будут выделены в куче Java вместе с объектом при создании экземпляра объекта;

  • Во-вторых, упомянутое здесь начальное значение «обычно» является нулевым значением типа данных. Предположим, что переменная класса определена какpublic static int value = 123;затем переменнаяvalueНачальное значение после этапа подготовки равно 0 вместо 123, потому что в это время не выполнялся ни один метод Java, а инструкция putstatic, присваивающая значение 123, хранится в методе конструктора класса() после компиляции программы, поэтому put Действие, которому присвоено значение 123, не будет выполняться до фазы инициализации.

Здесь упоминается, что в «нормальной ситуации» начальное значение равно нулю, поэтому будут некоторые «особые случаи»: если в таблице атрибутов поля поля класса есть атрибут ConstantsValue, значение переменной будет инициализировано на этапе подготовки как значение, на которое указывает свойство ConstantValue. Предположим, что определение значения переменной класса выше становитсяpublic static final int value = 123;, JavaC сгенерирует свойство ConstantValue для значения при компиляции, а виртуальная машина присвоит значение 123 в соответствии с настройкой ConstantValue на этапе подготовки.

Разобрать

Фаза разрешения — это процесс, посредством которого виртуальная машина заменяет символические ссылки в пуле констант прямыми ссылками. Я много раз упоминал символические ссылки и прямые ссылки, так что же такое символические ссылки и прямые ссылки?

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

  • Прямая ссылка: Прямая ссылка может быть указателем непосредственно на цель, относительным смещением или дескриптором, который может косвенно указывать на цель.

инициализация

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

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

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

Классы и загрузчики классов

Для любого класса его уникальность в виртуальной машине Java должна быть установлена ​​загрузчиком классов, который его загружает, и самим классом.Каждый загрузчик классов имеет независимое пространство имен классов. То есть: сравнение того, являются ли два класса «равными», имеет смысл только в том случае, если два класса загружаются одним и тем же загрузчиком классов, в противном случае, даже если два класса взяты из одного и того же файла классов, они идентичны. Виртуальная машина загружается, пока загрузчик классов, который их загружает, отличается, два класса не должны быть равными.

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

С точки зрения виртуальной машины Java существует только два разных загрузчика классов: один — Bootstrap ClassLoader, реализованный на C++ и являющийся частью самой виртуальной машины, другой — Bootstrap ClassLoader. которые реализованы на Java, не зависят от внешней среды виртуальной машины, и все они наследуются от абстрактных классов.java.lang.ClassLoader.

С точки зрения разработчика Java загрузчики классов можно разделить на:

  • Bootstrap ClassLoader: этот загрузчик классов отвечает за загрузку библиотеки классов, хранящейся в каталоге \lib, в память виртуальной машины. Java-программы не могут напрямую ссылаться на загрузчик класса запуска.Когда пользователь пишет собственный загрузчик класса, если ему необходимо делегировать запрос на загрузку загрузчику класса запуска, Nazhijie может вместо этого использовать значение null;

  • Расширение ClassLoader: Этот загрузчик классов созданsun.misc.Launcher$ExtClassLoaderРеализация, он отвечает за загрузку всех библиотек классов в каталог \lib\ext или по пути, указанному системной переменной java.ext.dirs, разработчики могут напрямую использовать загрузчик классов расширения;

  • Загрузчик классов приложений: Этот загрузчик классов созданsun.misc.Launcher$App-ClassLoaderвыполнить.getSystemClassLoader()Именно этот загрузчик классов возвращает метод, поэтому его еще называют системным загрузчиком классов. Он отвечает за загрузку библиотеки классов, указанной в пути к классам пользователя (ClassPath). Разработчики могут использовать этот загрузчик классов напрямую.Если приложение не настроило свой собственный загрузчик классов, как правило, это загрузчик классов по умолчанию в программе.

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

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

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

Преимущество этого в том, что класс Java имеет приоритетную иерархию со своим загрузчиком классов. Например, java.lang.Object, который помещается в rt.jar, независимо от того, какой загрузчик класса хочет загрузить этот класс, в конечном итоге он делегируется загрузчику запускаемого класса в верхней части модели для загрузки, поэтому класс Object используется в различных программах программы.Среда загрузчика классов - все тот же класс. Наоборот, если родительская модель делегирования не используется, и каждый загрузчик классов загружает ее сам, если пользователь напишет класс с именем java.lang.Object и поместит его в ClassPath программы, будет много разных Класс объекта, самое основное поведение в системе типов Java не гарантируется.

Модель родительского делегирования очень важна для обеспечения стабильности Java-программы, но ее реализация очень проста. Код для реализации родительской модели делегирования сосредоточен в методе loadClass() java.lang.ClassLoader. Логика очень проста. ясно: сначала проверьте, был ли он загружен, если нет, будет вызван метод loadClass () загрузчика родительского класса.Если родительский загрузчик пуст, загрузчик запускаемого класса будет использоваться в качестве родительского загрузчика по умолчанию. Если родительский класс не загружается, создайте исключение ClassNotFoundException, а затем вызовите его собственный метод findClass() для его загрузки.

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
    // 首先,检查请求的类是不是已经被加载过
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 如果父类抛出 ClassNotFoundException 说明父类加载器无法完成加载
        }

        if (c == null) {
            // 如果父类加载器无法加载,则调用自己的 findClass 方法来进行类加载
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

Структура файла класса и загрузка класса представлены здесь в двух последовательных статьях.В следующей статье давайте поговорим о «механизме выполнения байт-кода виртуальной машины».

Использованная литература:

  • Понимание виртуальной машины Java: расширенные функции JVM и рекомендации (2-е издание)

Если вам нравятся мои статьи, подписывайтесь на мой публичный аккаунтBaronTalk,Знай колонкуили вGitHubДобавьте звезду!