Ответ 1
Вы можете понять проверку байтового кода, используя эту диаграмму, подробно описанную в Oracle docs
Вы обнаружите, что проверка байтового кода происходит только один раз не дважды
На иллюстрации показан поток данных и управление с языка Java исходный код через компилятор Java, загрузчик классов и байтовым верификатором и, следовательно, на виртуальную машину Java, содержит систему интерпретатора и времени выполнения. Важным вопросом является что загрузчик классов Java и верификатор байт-кода не делают предположения об основном источнике потока байт-кода - код возможно, пришли из локальной системы или, возможно, прошли полпути вокруг планеты. Верификатор байт-кода действует как своего рода гейткипер: он гарантирует, что код, переданный интерпретатору Java, находится в состоянии соответствия быть выполненным и может работать без страха нарушить Java переводчик. Импортированный код не разрешается каким-либо образом выполнять пока он не пройдет проверку верификатора. Как только верификатор сделано несколько важных свойств:
- Нет операндов или нижних потоков
- Типы параметров всех команд байт-кода, как известно, всегда правильные.
- Доступ к полям объектов, как известно, является законным - закрытым, общедоступным или защищенным.
Пока вся эта проверка кажется мучительно детализированной, к тому времени верификатор байт-кода выполнил свою работу, интерпретатор Java может продолжайте, зная, что код будет работать надежно. Зная эти свойства делают интерпретатор Java намного быстрее, потому что он не должны что-то проверить. Нет проверок типа операнда и нет стека проверки переполнения. Таким образом, интерпретатор может работать на полной скорости без ущерба для надежности.
EDIT: -
Из Oracle Docs Раздел 5.3.2:
Когда метод loadClass загрузчика классов L вызывается с помощью имя N класса или интерфейса C для загрузки, L должен выполнить один из следующие две операции для загрузки C:
- загрузчик классов L может создать массив байтов, представляющий C как байты структуры ClassFile (§ 4.1); тогда он должен вызывать метод defineClass класса ClassLoader. Вызов defineClass заставляет виртуальную машину Java выводить класс или интерфейс обозначается N, используя L из массива байтов, используя алгоритм найдено в п. 5.3.5.
- Погрузчик классов L может делегировать загрузку C другому загрузчику классов L '. Это достигается путем передачи аргумента N прямо или косвенно на вызов метода на L ' (обычно это метод loadClass). Результатом вызова является С.
Как правильно прокомментировал Хольгер, пытаясь объяснить это подробнее с помощью пример:
static int factorial(int n)
{
int res;
for (res = 1; n > 0; n--) res = res * n;
return res;
}
Соответствующий байтовый код будет
method static int factorial(int), 2 registers, 2 stack slots
0: iconst_1 // push the integer constant 1
1: istore_1 // store it in register 1 (the res variable)
2: iload_0 // push register 0 (the n parameter)
3: ifle 14 // if negative or null, go to PC 14
6: iload_1 // push register 1 (res)
7: iload_0 // push register 0 (n)
8: imul // multiply the two integers at top of stack
9: istore_1 // pop result and store it in register 1
10: iinc 0, -1 // decrement register 0 (n) by 1
11: goto 2 // go to PC 2
14: iload_1 // load register 1 (res)
15: ireturn // return its value to caller
Обратите внимание, что большинство инструкций в JVM напечатаны.
Теперь вы должны заметить, что правильная работа JVM не гарантируется, если код не удовлетворяет хотя бы следующим условиям:
- Правильность текста: аргументы инструкции всегда имеют типы, ожидаемые инструкцией.
- Нет стека над потоком или под потоком: инструкция никогда не выдает аргумент o ff пустой стек, а также толкает результат на полный стек (размер которого равен равный максимальному размеру стека, объявленному для метода).
- Скрытие кода: счетчик программ всегда должен указывать код для метода, к началу действительной кодировки команд (не опускание конца кода метода, никаких ветвей в середине кодировки команд).
- Инициализация регистра: загрузка из регистра всегда должна выполняться в менее одного хранилища в этом регистре; другими словами, регистры, которые делают не соответствуют параметрам метода, не инициализируются по методу вход, и это ошибка для загрузки из неинициализированного регистра.
- Инициализация объекта: когда создается экземпляр класса C, один методов инициализации для класса C (соответствующих конструкторы для этого класса) должны быть вызваны перед классом экземпляр может использоваться.
Цель проверки байтового кода - проверить это условие раз и навсегда, путем статического анализа байтового кода во время загрузки. Байт-код, который проходит проверку, может быть выполнен быстрее.
Также следует отметить, что целью проверки байтового кода является перенос указанной выше вертификации из времени выполнения для загрузки.
Вышеприведенное объяснение было взято из Проверка байт-кода Java: алгоритмы и формализации