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

Java задняя часть JVM

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

Обзор

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

время загрузки класса

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

  1. При встрече с четырьмя инструкциями байт-кода new, getstatic, putstatic и invokestatic, если класс не был инициализирован, ему необходимо инициировать его инициализацию (инициализацию естественно существующей загрузки класса). Наиболее распространенные сценарии для этих четырех инструкций: при создании экземпляра объекта с ключевым словом new, при получении или установке статического поля класса (кроме измененных final) и при использовании статического метода класса.
  2. При использовании метода пакета java.lang.reflect для выполнения вызова отражения к классу, если класс не был инициализирован, запускается инициализация.
  3. При инициализации подкласса, когда обнаруживается, что его родительский класс не был инициализирован, сначала необходимо запустить инициализацию родительского класса.
  4. Когда виртуальная машина запускается, когда пользователю нужно указать основной класс (класс, содержащий основной метод) для выполнения, виртуальная машина сначала инициализирует этот класс.
  5. При использовании поддержки динамического языка JDK1.7, если последним результатом синтаксического анализа экземпляра java.lang.invoke.MethodHandle является дескриптор метода REF_getStatic, REF_putStatic и REF_invokeStatic, а класс, соответствующий этому дескриптору метода, не был инициализирован. , вам нужно сначала запустить его инициализацию.

процесс загрузки класса

нагрузка

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

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

Загрузка класса, не являющегося массивом, может быть выполнена либо с помощью загрузчика классов начальной загрузки, предоставленного виртуальной машиной, либо с помощью пользовательского загрузчика классов (то есть с переопределением метода loadClass загрузчика классов). Для классов-массивов ситуация иная, сам класс-массив создается не загрузчиком классов, а самой виртуальной машиной.

проверять

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

  • проверка формата файла
    На первом этапе проверяется, соответствует ли поток байтов спецификации формата файла класса и может ли он в настоящее время обрабатываться виртуальной машиной. Основными пунктами проверки на этом этапе являются:
  1. Начинается ли это с магического числа
  2. Входит ли версия в область обработки собственной виртуальной машины.
  3. Различные значения индекса, указывающие на константы, указывают на несуществующие константы
  4. ......
  • проверка метаданных
    Второй этап заключается в основном в выполнении семантического анализа информации о метаданных класса, чтобы убедиться в отсутствии метаданных, не соответствующих спецификации языка Java. Точки проверки:
  1. Есть ли у этого класса родительский класс
  2. Наследуется ли этот класс от классов, которые не могут наследоваться (классы, измененные final)
  3. Если этот класс не является абстрактным классом, реализует ли он методы, требуемые его родительским классом или унаследованным интерфейсом.
  4. ......
  • Проверка байт-кода
    Третий этап является наиболее сложным этапом в процессе проверки, и его основная цель состоит в том, чтобы проанализировать, является ли семантика программы законной или нет, посредством потока данных и потока управления. На этом этапе проверяется тело метода класса, чтобы гарантировать, что проверенный метод не нанесет вреда виртуальной машине при запуске:
  1. Гарантирует, что инструкции перехода не будут переходить к инструкциям байт-кода за пределами тела метода.
  2. Обеспечьте правильное преобразование типов в телах методов.
  3. ......
  • Проверка символьной ссылки
    Последний этап происходит, когда виртуальная машина преобразует символьную ссылку в прямую ссылку Это действие преобразования происходит на третьем этапе соединения - разрешении. Символические ссылки можно рассматривать как проверку различных символьных ссылок в постоянном пуле.Точками проверки являются:
  1. Найден ли соответствующий класс или интерфейс по полному имени, описанному строкой в ​​ссылке на символ.
  2. Доступность классов, полей и методов в символических ссылках может быть доступна текущему классу.
  3. ......

Подготовить

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

public static int value = 123

Начальное значение переменной value после фазы подготовки равно 0, а не 123, потому что в это время не запускается ни один java-метод, и когда компилируется инструкция putstatic, присваивающая значение 123, программа сохраняется в методе-конструкторе класса. поэтому операция присвоения значения 123 выполняется на этапе инициализации.
Как упоминалось выше, обычно это нулевое значение типа данных, но есть несколько особых случаев, которые отличаются:Если переменная класса окончательно изменена, на этапе подготовки переменная класса будет инициализирована до указанного значения..

public static final int value = 123;

На этапе подготовки значению value будет присвоено значение 123.

Разобрать

Фаза синтаксического анализа — это процесс, в котором виртуальная машина заменяет символические ссылки в пуле констант прямыми ссылками.. Спецификация виртуальной машины не указывает, когда выполнять фазу синтаксического анализа, а требует только ** выполнения anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokeestatic, invovirtual, ldc, ldc_w, multianewarray, new, putstatic Перед 16-ю инструкциями байт-кода, используемыми для управления символическими ссылками, используемые ими символические ссылки анализируются**. Поэтому виртуальная машина может определить, разрешать ли символическую ссылку, когда класс загружается загрузчиком, или разрешать символическую ссылку до того, как она будет использована по мере необходимости.
Действие синтаксического анализа в основном анализирует 7 типов символических ссылок, таких как классы или интерфейсы, поля, методы класса, методы интерфейса, типы методов, дескрипторы методов и квалификаторы сайта вызова.

  • Класс или разрешение интерфейса
    Предполагая, что класс, в котором находится текущий код, — D, если вы хотите преобразовать неразрешенную ссылку на символ N в прямую ссылку на класс или интерфейс C, процесс завершения всей фазы разрешения виртуальной машиной делится на следующие три шага:
  1. Если C не является типом массива, виртуальная машина передаст полное имя класса символической ссылки N загрузчику классов D для загрузки класса C. В процессе загрузки из-за необходимости проверки может быть запущена загрузка других классов.Когда в процессе загрузки возникает ошибка, процесс синтаксического анализа напрямую завершается сбоем.
  2. Если C является типом массива, а элементы массива также являются объектами, дескриптор для N будет иметь вид [Ljava/lang/Integer. Это загрузит тип элемента массива в соответствии с правилами первого пункта, а затем виртуальная машина сгенерирует объект массива, представляющий размеры и элементы массива.
  3. Если на предыдущих шагах нет ошибок, перед завершением синтаксического анализа необходимо проверить символическую ссылку, чтобы убедиться, что D имеет доступ к C. Если D не имеет доступа к C, генерируется исключение java.lang.IllegalAccessEroor.
  • разбор поля
    Символическая ссылка для разрешения неразрешенного поля. Сначала анализируется символическая ссылка CONSTANT_Class_info, индексируемая элементом class_index в таблице полей, то есть символическая ссылка класса или интерфейса, к которому принадлежит поле. Если при синтаксическом анализе символической ссылки этого класса или интерфейса возникнет исключение, разрешение поля завершится ошибкой. Если класс или интерфейс успешно разрешен, класс или интерфейс, к которому принадлежит это поле, будет представлен C, а затем C будет искать последующие поля:
  1. Если C сам содержит поле с тем же простым именем и дескриптором поля, что и целевое поле, вернуть прямая ссылка, конец поиска
  2. В противном случае, если интерфейс реализован на C, он будет рекурсивно искать каждый интерфейс и его родительский интерфейс снизу вверх в соответствии с отношением наследования, а затем выполнять шаг 1, чтобы найти
  3. В противном случае, если C не является классом объектов, выполните рекурсивный поиск его родительского класса снизу вверх в соответствии с отношением наследования, а затем выполните шаг 1, чтобы найти
  4. В противном случае поиск завершится с ошибкой java.lang.NoSuchFieldError.
  • разбор метода класса
    Первый шаг синтаксического анализа метода класса такой же, как синтаксический анализ поля. Вам также необходимо проанализировать ссылку на символ класса или интерфейса, к которому принадлежит метод, индексированный claaa_index таблицы методов класса. Если синтаксический анализ прошел успешно, используйте C для представления этого класса, а затем виртуальная машина выполняет следующие шаги для выполнения поиска метода класса:
  1. Находит метод в классе C как с простым именем, так и с дескриптором, соответствующим цели, если есть прямая ссылка, которая возвращает этот метод, поиск заканчивается
  2. В противном случае рекурсивно посмотрите в родительском классе класса C
  3. В противном случае посмотрите в интерфейсе или родительском интерфейсе класса C
  4. В противном случае поиск завершается ошибкой и выдается исключение java.lang.NoSuchMethodError.
  • Анализ метода интерфейса
    Разбор метода интерфейса похож на разбор метода класса и разбор метода класса, который здесь больше не является избыточным.

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

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

  1. Метод генерируется операцией копирования всех переменных класса в классе, автоматически собираемом компилятором, и все операторы в блоке статических операторов (static{}) объединяются. Статический блок операторов может получить доступ только к переменным, определенным до статического оператора, а переменные, определенные после него, могут быть назначены только в статическом блоке операторов и недоступны.
  2. В отличие от конструктора класса, методу не нужно явно вызывать конструктор родительского класса.Виртуальная возможность гарантирует, что метод родительского класса будет выполнен до того, как будет выполнен метод дочернего класса.
  3. Методы не требуются.Если в классе нет блоков статических операторов и операций присваивания переменных, компилятор может не генерировать методы для этого класса.
  4. Виртуальная машина обеспечивает правильную блокировку и синхронизацию методов класса в многопоточной среде.Если несколько потоков инициализируют класс одновременно, только один поток выполняет метод.

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

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

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

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

  • Запустите загрузчик классов
    Этот класс отвечает за загрузку библиотеки классов, размещенной в каталоге \lib и распознаваемой виртуальной машиной (судя по имени файла, библиотека классов, имя которой не совпадает, не будет загружена, даже если она помещена в каталог JAVA_HOME>\lib). lib) в виртуальную машину, в память. Java-программа не может напрямую ссылаться на загрузчик класса запуска.

  • Расширенный загрузчик классов
    Он отвечает за загрузку всех библиотек классов в каталоге \lib\ext, и разработчики могут напрямую использовать расширенный загрузчик классов.

  • Загрузчик класса эталонной программы
    Он отвечает за загрузку библиотеки классов, указанной в пользовательском пути к классам (ClassPath), и разработчики могут использовать его напрямую. Если программа не настраивает свой собственный загрузчик классов, в общем случае это загрузчик классов программы по умолчанию.

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

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