Преобразование результата компиляции кода из собственного машинного кода в байт-код — это небольшой шаг в эволюции форматов хранения, но гигантский шаг в эволюции языков программирования.
Обзор
Виртуальная машина загружает данные, описывающие класс, из файла Class в память, проверяет, преобразует, анализирует и инициализирует данные и, наконец, формирует тип Java, который может использоваться виртуальной машиной. виртуальная машина.
время загрузки класса
Весь жизненный цикл загрузки класса включает семь процессов: загрузка, проверка, подготовка, синтаксический анализ, инициализация, использование и выгрузка, из которых проверка, подготовка и синтаксический анализ в совокупности называются подключением.
Виртуальная машина не имеет обязательных ограничений на загрузку классов.Но для фазы инициализации спецификация виртуальной машины строго оговаривает, что есть только 5 случаев, в которых класс должен быть инициализирован сразу (загрузка, проверка, подготовка и инициализация естественно должны быть завершены до инициализации):
- При встрече с четырьмя инструкциями байт-кода new, getstatic, putstatic и invokestatic, если класс не был инициализирован, ему необходимо инициировать его инициализацию (инициализацию естественно существующей загрузки класса). Наиболее распространенные сценарии для этих четырех инструкций: при создании экземпляра объекта с ключевым словом new, при получении или установке статического поля класса (кроме измененных final) и при использовании статического метода класса.
- При использовании метода пакета java.lang.reflect для выполнения вызова отражения к классу, если класс не был инициализирован, запускается инициализация.
- При инициализации подкласса, когда обнаруживается, что его родительский класс не был инициализирован, сначала необходимо запустить инициализацию родительского класса.
- Когда виртуальная машина запускается, когда пользователю нужно указать основной класс (класс, содержащий основной метод) для выполнения, виртуальная машина сначала инициализирует этот класс.
- При использовании поддержки динамического языка JDK1.7, если последним результатом синтаксического анализа экземпляра java.lang.invoke.MethodHandle является дескриптор метода REF_getStatic, REF_putStatic и REF_invokeStatic, а класс, соответствующий этому дескриптору метода, не был инициализирован. , вам нужно сначала запустить его инициализацию.
процесс загрузки класса
нагрузка
На этапе загрузки класса виртуальная машина должна выполнить следующие три события:
- Получить двоичный поток байтов, который определяет класс по его полному имени
- Преобразуйте статическую структуру хранения, представленную потоком байтов, в структуру данных времени выполнения области метода.
- Создайте объект java.lang.Class, представляющий этот класс в области методов в качестве записи доступа к различным данным этого класса в области методов.
Загрузка класса, не являющегося массивом, может быть выполнена либо с помощью загрузчика классов начальной загрузки, предоставленного виртуальной машиной, либо с помощью пользовательского загрузчика классов (то есть с переопределением метода loadClass загрузчика классов). Для классов-массивов ситуация иная, сам класс-массив создается не загрузчиком классов, а самой виртуальной машиной.
проверять
Проверка – это первый шаг на этапе подключения. Цель состоит в том, чтобы убедиться, что информация, содержащаяся в потоке байтов файла Class, соответствует требованиям текущей виртуальной машины, а входной поток байтов может быть правильно проанализирован и сохранен в область метода.. Этап проверки в основном включает следующие четыре этапа: проверку формата файла, проверку метаданных, проверку байт-кода и проверку ссылки на символ.
- проверка формата файла
На первом этапе проверяется, соответствует ли поток байтов спецификации формата файла класса и может ли он в настоящее время обрабатываться виртуальной машиной. Основными пунктами проверки на этом этапе являются:
- Начинается ли это с магического числа
- Входит ли версия в область обработки собственной виртуальной машины.
- Различные значения индекса, указывающие на константы, указывают на несуществующие константы
- ......
- проверка метаданных
Второй этап заключается в основном в выполнении семантического анализа информации о метаданных класса, чтобы убедиться в отсутствии метаданных, не соответствующих спецификации языка Java. Точки проверки:
- Есть ли у этого класса родительский класс
- Наследуется ли этот класс от классов, которые не могут наследоваться (классы, измененные final)
- Если этот класс не является абстрактным классом, реализует ли он методы, требуемые его родительским классом или унаследованным интерфейсом.
- ......
- Проверка байт-кода
Третий этап является наиболее сложным этапом в процессе проверки, и его основная цель состоит в том, чтобы проанализировать, является ли семантика программы законной или нет, посредством потока данных и потока управления. На этом этапе проверяется тело метода класса, чтобы гарантировать, что проверенный метод не нанесет вреда виртуальной машине при запуске:
- Гарантирует, что инструкции перехода не будут переходить к инструкциям байт-кода за пределами тела метода.
- Обеспечьте правильное преобразование типов в телах методов.
- ......
- Проверка символьной ссылки
Последний этап происходит, когда виртуальная машина преобразует символьную ссылку в прямую ссылку Это действие преобразования происходит на третьем этапе соединения - разрешении. Символические ссылки можно рассматривать как проверку различных символьных ссылок в постоянном пуле.Точками проверки являются:
- Найден ли соответствующий класс или интерфейс по полному имени, описанному строкой в ссылке на символ.
- Доступность классов, полей и методов в символических ссылках может быть доступна текущему классу.
- ......
Подготовить
Подготовительный этап — это этап формального выделения памяти под переменные класса (переменные, модифицированные статическими, исключая переменные экземпляра) и установка начальных значений переменных класса, которые выделяются в области методов. Кроме того, упомянутое здесь начальное значение обычно относится к нулевому значению типа данных.
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, процесс завершения всей фазы разрешения виртуальной машиной делится на следующие три шага:
- Если C не является типом массива, виртуальная машина передаст полное имя класса символической ссылки N загрузчику классов D для загрузки класса C. В процессе загрузки из-за необходимости проверки может быть запущена загрузка других классов.Когда в процессе загрузки возникает ошибка, процесс синтаксического анализа напрямую завершается сбоем.
- Если C является типом массива, а элементы массива также являются объектами, дескриптор для N будет иметь вид [Ljava/lang/Integer. Это загрузит тип элемента массива в соответствии с правилами первого пункта, а затем виртуальная машина сгенерирует объект массива, представляющий размеры и элементы массива.
- Если на предыдущих шагах нет ошибок, перед завершением синтаксического анализа необходимо проверить символическую ссылку, чтобы убедиться, что D имеет доступ к C. Если D не имеет доступа к C, генерируется исключение java.lang.IllegalAccessEroor.
- разбор поля
Символическая ссылка для разрешения неразрешенного поля. Сначала анализируется символическая ссылка CONSTANT_Class_info, индексируемая элементом class_index в таблице полей, то есть символическая ссылка класса или интерфейса, к которому принадлежит поле. Если при синтаксическом анализе символической ссылки этого класса или интерфейса возникнет исключение, разрешение поля завершится ошибкой. Если класс или интерфейс успешно разрешен, класс или интерфейс, к которому принадлежит это поле, будет представлен C, а затем C будет искать последующие поля:
- Если C сам содержит поле с тем же простым именем и дескриптором поля, что и целевое поле, вернуть прямая ссылка, конец поиска
- В противном случае, если интерфейс реализован на C, он будет рекурсивно искать каждый интерфейс и его родительский интерфейс снизу вверх в соответствии с отношением наследования, а затем выполнять шаг 1, чтобы найти
- В противном случае, если C не является классом объектов, выполните рекурсивный поиск его родительского класса снизу вверх в соответствии с отношением наследования, а затем выполните шаг 1, чтобы найти
- В противном случае поиск завершится с ошибкой java.lang.NoSuchFieldError.
- разбор метода класса
Первый шаг синтаксического анализа метода класса такой же, как синтаксический анализ поля. Вам также необходимо проанализировать ссылку на символ класса или интерфейса, к которому принадлежит метод, индексированный claaa_index таблицы методов класса. Если синтаксический анализ прошел успешно, используйте C для представления этого класса, а затем виртуальная машина выполняет следующие шаги для выполнения поиска метода класса:
- Находит метод в классе C как с простым именем, так и с дескриптором, соответствующим цели, если есть прямая ссылка, которая возвращает этот метод, поиск заканчивается
- В противном случае рекурсивно посмотрите в родительском классе класса C
- В противном случае посмотрите в интерфейсе или родительском интерфейсе класса C
- В противном случае поиск завершается ошибкой и выдается исключение java.lang.NoSuchMethodError.
- Анализ метода интерфейса
Разбор метода интерфейса похож на разбор метода класса и разбор метода класса, который здесь больше не является избыточным.
инициализация
Фаза инициализации является последним шагом в процессе загрузки класса.В предыдущем процессе загрузки класса, за исключением того, что загрузчик может быть автоматически определен для участия в процессе загрузки класса на этапе загрузки, остальные действия полностью контролируются виртуальной машиной. На этапе инициализации фактически выполняется Java-код, определенный в классе..
На этапе подготовки переменной присваивается нулевое значение, требуемое системой, а на этапе инициализации переменные класса и другие ресурсы инициализируются в соответствии с субъективным планом, составленным программистом через программу. Другими словами, фаза инициализации — это процесс выполнения метода конструктора класса.
- Метод генерируется операцией копирования всех переменных класса в классе, автоматически собираемом компилятором, и все операторы в блоке статических операторов (static{}) объединяются. Статический блок операторов может получить доступ только к переменным, определенным до статического оператора, а переменные, определенные после него, могут быть назначены только в статическом блоке операторов и недоступны.
- В отличие от конструктора класса, методу не нужно явно вызывать конструктор родительского класса.Виртуальная возможность гарантирует, что метод родительского класса будет выполнен до того, как будет выполнен метод дочернего класса.
- Методы не требуются.Если в классе нет блоков статических операторов и операций присваивания переменных, компилятор может не генерировать методы для этого класса.
- Виртуальная машина обеспечивает правильную блокировку и синхронизацию методов класса в многопоточной среде.Если несколько потоков инициализируют класс одновременно, только один поток выполняет метод.
Классы и загрузчики классов
Команда разработчиков виртуальной машины реализует действие по получению двоичного потока байтов этого класса через полное имя класса на этапе загрузки класса и реализует его вне виртуальной машины Java, чтобы разработчики могли решить, как получить требуемый класс. и реализовать это действие Модули кода называются «загрузчиками классов».
Для любого класса загрузчику классов, который его загружает, и самому классу необходимо определить уникальность виртуальной машины, на которой он находится. С точки зрения непрофессионала, сравнение двух классов на предмет равенства имеет смысл только при наличии одного и того же загрузчика классов. В противном случае, даже если два класса происходят из одного и того же файла классов и загружаются одной и той же виртуальной машиной, пока загрузчик В противном случае два класса не могут быть равными.
Модель родительского делегирования
С точки зрения виртуальной машины существует только два разных загрузчика классов: один — загрузчик запускаемого класса, являющийся частью виртуальной машины, другой — другие загрузчики классов, независимые от виртуальной машины, и все наследуются от абстрактного класс java.lang.ClassLoader.
С точки зрения разработчика, большинство Java-программ используют загрузчики классов, предоставляемые следующими тремя системами:
-
Запустите загрузчик классов
Этот класс отвечает за загрузку библиотеки классов, размещенной в каталоге\lib и распознаваемой виртуальной машиной (судя по имени файла, библиотека классов, имя которой не совпадает, не будет загружена, даже если она помещена в каталог JAVA_HOME>\lib). lib) в виртуальную машину, в память . Java-программа не может напрямую ссылаться на загрузчик класса запуска. -
Расширенный загрузчик классов
Он отвечает за загрузку всех библиотек классов в каталоге\lib\ext, и разработчики могут напрямую использовать расширенный загрузчик классов. -
Загрузчик класса эталонной программы
Он отвечает за загрузку библиотеки классов, указанной в пользовательском пути к классам (ClassPath), и разработчики могут использовать его напрямую. Если программа не настраивает свой собственный загрузчик классов, в общем случае это загрузчик классов программы по умолчанию.
- Рабочий процесс родительской модели делегирования:
Если загрузчик класса получает запрос на загрузку класса, он не загружает сам класс, а делегирует запрос для загрузки своему загрузчику родительского класса. попадают в загрузчик классов запуска верхнего уровня.Только когда родительский загрузчик классов загружает запрос (нужный класс не найден в его области поиска), дочерний загрузчик попытается загрузить его самостоятельно. Преимущество использования родительского делегирования заключается в том, что классы Java имеют приоритетную иерархию с их загрузчиком классов.