Кратко: виртуальная машина загружает данные, описывающие класс, из файла класса в память, проверяет, преобразует, анализирует и инициализирует данные и, наконец, формирует тип Java, который может быть непосредственно использован виртуальной машиной. Механизм виртуальной машины.
Давайте подробно рассмотрим процесс загрузки класса:
Класс начинается с загрузки в память и выгружается из памяти, проходя черезЗагрузить, подключить, инициализировать, использоватьчетыре этапа, в том числесоединятьсодержит сноваПроверить, подготовить, разобратьтри шага. Шаги обычно идут в порядке, показанном на диаграмме, но сам язык Java поддерживает привязки во время выполнения, поэтомуФаза синтаксического анализа также может быть выполнена после инициализации.. Приведенная выше последовательность является лишь начальной последовательностью, в реальном процессе она пересекается, и проверка может уже начаться в процессе загрузки.
время загрузки класса
Первое, что нужно знать, это когда класс должен быть загружен.Спецификация виртуальной машины Java не ограничивает это, но оговаривает, что класс должен быть загружен.инициализацияОчевидно, что из пяти случаев загрузка, проверка и подготовка должны выполняться до инициализации.Подробно описаны следующие пять случаев:
Наиболее распространенные сценарии для 4 Bytecode Induror на случай 1 в Java являются:
1. Когда новый объект
2. Установить или получить статическое поле класса (удалить статическое поле, которое помещается в пул констант при окончательной модификации)
3. Вызвать статический метод класса
процесс загрузки класса
Ниже мы разберем каждый процесс загрузки класса шаг за шагом
1. Загрузить
нагрузкаЭто первый шаг во всем процессе загрузки класса.Если вам нужно создать класс или интерфейс, вам необходимо создать внутреннее представление, соответствующее требованиям реализации виртуальной машины, в области методов виртуальной машины Java. Вообще говоря, создание класса инициируется другим классом или интерфейсом.Это относится к классу, который должен быть создан через его собственный пул констант времени выполнения, или это может быть связано с вызовом некоторых методов в основной библиотеке классов Java, таких как отражение , и т.д. .
Вообще говоря, загрузка делится на следующие этапы:
- Получить двоичный поток байтов класса по его полному имени
- Преобразуйте статическую структуру хранения, представленную этим потоком байтов, в структуру данных времени выполнения области метода.
- Создайте объект java.lang.Class, представляющий этот класс в памяти в качестве записи доступа к различным данным этого класса в области методов.
Создайте класс с именем C. Если C не является типом массива, то он может загрузить двоичное представление C (т. е. файл класса) через загрузчик классов. Если это массив, он создается виртуальной машиной Java, и виртуальная машина рекурсивно использует вышеупомянутый процесс загрузки для непрерывной загрузки компонентов массива.
Виртуальная машина Java поддерживает два типа загрузчиков классов:
- Bootstrap ClassLoader
- Пользовательский загрузчик классов
Определяемые пользователем загрузчики классов должны быть абстрактными классами.ClassLoaderЭкземпляр подкласса . Приложения используют определяемые пользователем загрузчики классов для расширения функциональности виртуальной машины Java, поддержки динамической загрузки и создания классов. Например, на первом этапе загрузки, чтобы получить поток двоичных байтов, через пользовательский загрузчик классов мы можем загрузить из сети, динамически сгенерировать или извлечь информацию о классе из зашифрованного файла.
О загрузчике классов откроется новое описание статьи.
2. Подтвердить
Проверка — это первый шаг в компоновке, гарантирующий, что двоичное представление класса или интерфейса является структурно правильным, что гарантирует, что информация, содержащаяся в потоке байтов, безопасна для виртуальной машины. Правила этапа проверки в спецификации виртуальной машины Java также расширяются, но обычно выполняются следующие четыре действия проверки.
1 . проверка формата файла: в первую очередь проверьте, соответствует ли поток байтов спецификации формата файла класса и может ли он обрабатываться текущей версией виртуальной машины.
Основные точки проверки:
- магическое ли число
0xCAFEBABE
начало - Находятся ли основной и дополнительный номера версий в пределах диапазона обработки текущей виртуальной машины.
- Имеет ли константа пула констант неподдерживаемый тип (проверьте флаг тега константы)
- Указывает ли какое-либо из различных значений индекса, указывающих на константу, на несуществующую константу или константу, которая не соответствует типу
- Есть ли в константе типа CONSTANT_Utf8_info данные, не соответствующие кодировке UTF8
- Есть ли какая-либо удаленная или дополнительная информация в каждой части файла класса и самого файла.
...
На самом деле проверяются не только они.Что касается формата файла класса, вы можете обратиться к моемуГлубокое понимание формата файла класса JVM, проверка на этом этапе основана на бинарном потоке байтов, только после прохождения проверки формата файла поток байтов будет храниться в области методов памяти.
2 . проверка метаданных: в основном выполнять семантический анализ информации, описанной байт-кодом, чтобы убедиться, что информация, которую он предоставляет, соответствует требованиям спецификации языка Java.
Основные точки проверки:
- Имеет ли класс родительский класс (только объект Object не имеет родительского класса, остальные имеют)
- Наследует ли класс классы, которые не могут наследоваться (классы, измененные final)
- Если этот класс не является абстрактным классом, реализует ли он все методы, необходимые для реализации в его родительском классе или интерфейсе.
- Конфликтуют ли поля и методы в классе с родительским классом (например, затирается финальное поле родительского класса, и есть ли перегрузка методов, не соответствующая правилам, например, параметры методов совпадают, но тип возвращаемого значения другой)
...
3 . Проверка байт-кода: в основном посредством анализа потока данных и потока управления определяется, что семантика программы является законной и логичной. На втором этапе, после проверки типа данных в информации метаданных, проверка байт-кода проверит и проанализирует тело метода класса, чтобы гарантировать, что метод проверенного класса не причинит вреда виртуальной машине во время выполнения безопасного события.
Есть:
- Убедитесь, что тип данных стека операндов и последовательность кода инструкции могут работать вместе в любое время.Например, подобная ситуация не произойдет: int данные в стеке операндов, но при использовании они загружаются в локальную переменную как длинный тип
- Гарантирует, что переходы не переходят к инструкциям байт-кода за пределами тела метода.
- Убедитесь, что преобразование типов внутри тела метода допустимо. Например, допустимо назначать подкласс родительскому классу, но незаконно назначать родительский класс подклассу или другим типам, которые не имеют отношений наследования.
-
Проверка символьной ссылки: последний этап проверки происходит, когда виртуальная машина преобразует символическую ссылку в прямую ссылку. Это действие преобразования будет выполняться на третьем этапе фазы разбора соединения. Символическая ссылка — это проверка соответствия информации, отличной от самого класса (различные символические ссылки в пуле констант).
Обычно бывают:
- Найден ли соответствующий класс по полному имени, описанному строкой в ссылке на символ
- Существует ли дескриптор поля, соответствующий методу, методу и полю, описанным простым именем в указанном классе.
- Может ли текущий класс получить доступ (частный, общедоступный, защищенный, по умолчанию) к классам, методам и полям в символических ссылках.
Цель проверки символьной ссылки состоит в том, чтобы гарантировать, что действие синтаксического анализа может быть выполнено нормально. .NoSuchFieldError, java.lang.NoSuchMethodError и т. д.
Этап проверки очень важен, но не обязательно необходим.Если весь код Jiying использовался и неоднократно проверялся, то параметры виртуальной машины можно передать.-Xverify: none
чтобы отключить проверку и ускорить время загрузки класса.
3. Подготовьте
Задача этапа подготовки — выделить место для статических полей класса или интерфейса и инициализировать эти поля по умолчанию. На этом этапе не будут выполняться никакие инструкции байт-кода виртуальной машины, и эти поля будут инициализированы только на этапе инициализации, поэтому этап подготовки не будет выполнять эти действия. Допустим есть:
public static int value = 123;
Начальное значение value на этапе подготовки равно 0 вместо 123. Только на этапе инициализации значение будет равно 0.
Вот посмотрите на нулевое значение всех примитивных типов в Java:
тип данных | нулевое значение |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
char | '\u0000' |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
Особым случаем является то, что если таблица атрибутов поля содержит атрибут ConstantValue, значение переменной на этапе подготовки будет инициализировано значением, указанным атрибутом ConstantValue, таким как приведенное выше значение, если оно определено следующим образом:
public static final int value = 123;
Во время компиляции value с самого начала указывает на ConstantValue, поэтому во время подготовки значение value уже равно 123.
4. Анализ
Фаза синтаксического анализа — это процесс замены символических ссылок в пуле констант прямыми ссылками, а символические ссылки — это ссылки в файле класса.CONSTANT_Class_info, CONSTANT_Fieldref_info,CONSTANT_Methodref_infoконстанты одного типа. Давайте посмотрим на определения символических ссылок и прямых ссылок.
Символические ссылки: Символическая ссылка описывает цель, на которую ссылаются, как набор символов, который может быть любой формой литерала, если цель может быть однозначно расположена. Символические ссылки не зависят от структуры памяти, поэтому объект, на который делается ссылка, не обязательно должен загружаться в память. Структура памяти различных реализаций виртуальных машин может быть разной, но принятые символические ссылки должны быть согласованы, поскольку буквальная форма символических ссылок четко определена в формате файла класса.
Прямые ссылки: В случае прямой ссылки указатель непосредственно на цель, относительное смещение или дескриптор, который может косвенно указывать на цель. Прямые ссылки связаны со структурой памяти, реализованной виртуальной машиной, и прямые ссылки, переведенные из одной и той же символической ссылки на разные виртуальные машины, обычно не совпадают. Если есть прямая ссылка, она уже должна существовать в памяти.
Следующие инструкции виртуальной машины Java будут указывать символическую ссылку на пул констант времени выполнения, и выполнение любой инструкции должно разрешать ее символическую ссылку:
Очень часто для одного и того же символа выполняется несколько запросов на синтаксический анализ.За исключением инструкции invokedynamic, виртуальная машина в основном кэширует результат первого синтаксического анализа, и когда он встречается позже, на него напрямую ссылаются, чтобы избежать дублирования действий по синтаксическому анализу.
Для инструкций invokedynamic вышеуказанные правила не выполняются. Обнаружение ссылки на символ, которая была разрешена ранее инструкцией invokedynamic, не означает, что разрешенный результат также действителен для других инструкций invokedynamic. Это определяется семантикой инструкции invokedynamic, которая изначально используется для поддержки динамического языка, то есть действие синтаксического анализа не будет выполнено до тех пор, пока программа фактически не запустит эту инструкцию. Другие команды являются «статическими» и могут быть проанализированы сразу после завершения этапа регистрации и до начала выполнения кода.
Вот несколько основных анализов:
Анализ классов и интерфейсов: Предполагая, что виртуальная машина Java ссылается на класс N или интерфейс C в теле метода класса D, будут выполнены следующие шаги:
- Если C не является типом массива, определяющий загрузчик классов D используется для создания класса N или интерфейса C. Любые исключения во время загрузки можно рассматривать как ошибки разрешения класса и интерфейса.
- Если C является типом массива, а его тип элемента является ссылочным типом. Затем символическая ссылка на класс или интерфейс, представляющий тип элемента, разрешается рекурсивными вызовами.
- Проверьте права доступа C, если у D нет прав доступа к C, он выкинет
java.lang.IllegalAccessError
аномальный.
разбор поля:
Чтобы разрешить неразрешенную ссылку на символ поля, прежде всегоCONSTANT_Class_info
Символьные ссылки анализируются, и те, кто не может вспомнить здесь, могут продолжить просмотрГлубокое понимание формата файла класса JVM, то есть символическая ссылка на класс или интерфейс, которому принадлежит поле. Если во время разрешения этой ссылки на класс или интерфейсный символ возникает какое-либо исключение, разрешение поля завершится ошибкой. Если синтаксический анализ будет выполнен, это будетКласс или интерфейс, которому принадлежит это полеОбозначается через C, спецификация виртуальной машины требует, чтобы следовать следующие этапы для поиска последующих полей C.
1. Если в самом C содержится поле, простое имя и дескриптор которого соответствуют цели, прямая ссылка на это поле возвращается напрямую, и поиск завершается.
2. В противном случае, если интерфейс реализован на C, он будет рекурсивно искать каждый интерфейс и его родительский интерфейс снизу вверх в соответствии с отношением наследования.Если интерфейс содержит поля, чьи простые имена и дескрипторы полей соответствуют цели, прямая ссылка в это поле возвращается, и поиск заканчивается.
3. Или же, если C неjava.lang.Object
Если родительский класс ищется рекурсивно снизу вверх в соответствии с отношением наследования, если класс содержит
Если есть поле, простое имя и дескриптор которого соответствуют цели, возвращается прямая ссылка на это поле, и поиск завершается.
4. Если такового нет, поиск проваливается и выходит, выбрасываяjava.lang.NoSuchFieldError
аномальный. Если ссылка возвращается, также нужно проверить доступ, если доступа нет, он выкинетjava.lang.IllegalAccessError
аномальный.
В реальной реализации требования могут быть более строгими, и компилятор может отказаться от компиляции, если одно и то же имя поля появляется как в родительском классе, так и в интерфейсе на C.
разбор метода класса
Анализ метода класса также заключается в том, чтобы сначала проанализировать ссылку на символ класса или интерфейса, к которому принадлежит метод, индексированный в элементе class_index в таблице методов класса. Мы по-прежнему используем C для представления проанализированного класса, а затем виртуальная машина выполнит описанные ниже шаги, чтобы выполнить последующие поиски методов класса для C.
1. Сначала проверьте, является ли C, на который ссылается метод, классом или интерфейсом.Если это интерфейс, то ссылка на метод выдастIncompatibleClassChangeError
аномальный
2. Во время процесса ссылки на метод будет проверено, содержит ли C и его родительские классы этот метод.Если действительно есть метод в C с тем же именем, что и указанная ссылка на метод, и объявление является сигнатурным полиморфным методом, то Процесс поиска метода считается успешным, и все классы, упомянутые в дескрипторе метода, также должны быть разрешены. Для C нет необходимости объявлять метод, используя дескриптор, указанный в ссылке на метод.
3. В противном случае, если C-объявленный метод имеет то же имя и дескриптор, что и ссылка на метод, поиск метода также добивается успеха.
4. Если C имеет родительский класс, то рекурсивно найти прямой родительский класс C в соответствии с методом на шаге 2.
5. В противном случае выполните рекурсивный поиск в списке интерфейсов, реализованных классом C, и их родительских интерфейсов, чтобы увидеть, есть ли метод, простое имя и дескриптор которого соответствуют цели.Если есть соответствующий метод, это означает, что класс C является абстрактным классом. , поиск заканчивается и выдаетjava.lang.AbstractMethodError
аномальный.
- В противном случае объявленный метод завершается ошибкой и выдает
java.lang.NoSuchMethodError
.
Наконец, если процесс поиска успешно возвращает прямую ссылку, метод будет проверен на наличие разрешений.Если будет обнаружено, что метод не имеет прав доступа, он выдаст ошибкуjava.lang.IllegalAccessError
аномальный.
Анализ метода интерфейса
Метод интерфейса также должен проанализировать ссылку на символ класса или интерфейса, к которому принадлежит метод, проиндексированный в элементе class_index таблицы методов интерфейса. машина будет следовать приведенным ниже шагам для выполнения последующих методов интерфейса.
1. В отличие от синтаксического анализа метода класса, если индекс C, соответствующий class_index, найден в таблице методов интерфейса как класс, а не интерфейс, он будет выбран напрямую.java.lang.IncompatibleClassChangeError
аномальный.
2. В противном случае выяснить, существует ли метод, простое имя и дескриптор которого соответствуют цели в интерфейсе C, и если да, то вернуть прямую ссылку на этот метод, и поиск завершается.
3. В противном случае рекурсивно искать в родительском интерфейсе интерфейса C до тех пор, покаjava.lang.Object
Пока что смотрите, есть ли метод, простое имя и дескриптор которого соответствуют цели, и если да, возвращайте прямую ссылку на этот метод, и поиск завершается.
4. В противном случае метод declare завершается ошибкой и выдаетjava.lang.NoSuchMethodError
аномальный.
Поскольку методы интерфейса по умолчанию общедоступны, проблемы с правами доступа нет, и он в принципе не будет бросатьjava.lang.IllegalAccessError
аномальный.
5. Инициализация
Инициализация является последним этапом загрузки класса.На предыдущем этапе, за исключением этапа загрузки, который может быть загружен определяемым пользователем загрузчиком классов, на остальных в основном доминирует виртуальная машина. Но на этапе инициализации фактически выполняется java-код, написанный пользователем.
На этапе подготовки переменным присваиваются начальные значения, но на этапе инициализации все переменные повторно инициализируются в соответствии с кодом, написанным пользователем. С другой точки зрения, фаза инициализации заключается в выполнении конструктора класса.<clinit>()
процесс метода.
<clinit>()
Метод генерируется компилятором, автоматически собирающим действие присваивания всех переменных класса в классе и объединяющим операторы в статический блок операторов (статический блок операторов).Порядок, в котором компилятор собирает, определяется порядком, в котором операторы появляются в исходном файле.Только переменные, определенные до блока статических операторов, могут быть доступны в блоке статических операторов, а переменные, определенные после него, могут быть доступны в блоке статических операторов. предыдущий блок статических операторов, присваивание, но без доступа.
public class Test {
static {
i=0; //可以赋值
System.out.print(i); //编译器会提示“非法向前引用”
}
static int i=1;
}
<clinit>()
Методы и конструкторы классов<init>()
Метод другой, ему не нужно явно вызывать конструктор родительского класса, виртуальная возможность хранится в подклассе<clinit>()
Перед выполнением метода родительский класс<clinit>()
был выполнен, поэтому первый выполняется в виртуальной машине<clinit>()
определенно естьjava.lang.Object
из.
также из-за<clinit>()
Порядок выполнения, поэтому блок статических операторов в родительском классе лучше, чем операция присваивания переменных в дочернем классе, поэтому в следующем сегменте кода значение B будет равно 2.
static class Parent {
public static int A=1;
static {
A=2;
}
}
static class Sub extends Parent{
public static int B=A;
}
public static void main(String[] args) {
System.out.println(Sub.B);
}
<clinit>()
Методы не нужны для классов.Если в классе нет ни блока статических операторов, ни действия присваивания статической переменной, то компилятор не сгенерирует их для класса.<clinit>()
метод.
Блоки статических операторов нельзя использовать в интерфейсах, но разрешены операции присваивания с инициализацией переменных, поэтому интерфейсы генерируются как классы.<clinit>()
метод, но в интерфейсе<clinit>()
Нет необходимости сначала выполнять родительский класс, и родительский интерфейс будет инициализирован только тогда, когда используются переменные, определенные в родительском классе. Кроме,Класс реализации интерфейса не будет выполнять реализацию интерфейса при его инициализации.<clinit>()
метод.
Виртуальная возможность гарантирует класс<clinit>()
Методы могут быть правильно связаны и синхронизированы в многопоточной среде. Если несколько потоков инициализируют класс, будет выполняться только один поток.<clinit>()
метод, другие потоки должны ждать.
6. Виртуальная машина Java завершает работу
Общие условия выхода виртуальной машины Java таковы: некоторые потоки вызывают метод выхода класса Runtime или класса System или метод halt класса Runtime, и менеджер безопасности Java также разрешает эти операции выхода или остановки.
Кроме того, спецификация JNI (собственный интерфейс Java) также описывает процесс выхода из виртуальной машины Java при использовании API JNI для загрузки и выгрузки (Load & Unload) виртуальной машины Java.