Что такое фрейм стековой карты
Недавно я изучал спецификации виртуальных машин Java (JVMS), чтобы попытаться лучше понять, что заставляет мои программы работать, но я нашел раздел, который мне не совсем понятен...
Раздел 4.7.4 описывает атрибут StackMapTable, и в этом разделе документ подробно описывает фреймы стековой карты. Проблема в том, что это немного многословно, и я учусь лучше всего на примере; не читая.
Я понимаю, что первый фрейм карты стека получен из дескриптора метода, но я не понимаю, как (что якобы здесь объясняется). Кроме того, я не совсем понимаю, что делают фреймы стековой карты. Я бы предположил, что они похожи на блоки в Java, но кажется, что вы не можете иметь фреймы стековых карт друг в друге.
Во всяком случае, у меня есть два конкретных вопроса:
- Что делают фреймы стековой карты?
- Как создается первый кадр карты стека?
и один общий вопрос:
- Может ли кто-нибудь дать объяснение менее многословно и легче для понимания, чем приведенное в JVMS?
1 ответ
Java требует, чтобы все загруженные классы были проверены, чтобы поддерживать безопасность песочницы и гарантировать безопасность кода для оптимизации. Обратите внимание, что это делается на уровне байт-кода, поэтому проверка не проверяет инварианты языка Java, а просто подтверждает, что байт-код имеет смысл в соответствии с правилами для байт-кода.
Помимо прочего, проверка байт-кода гарантирует, что инструкции правильно сформированы, что все переходы к действительным инструкциям внутри метода и что все инструкции работают со значениями правильного типа. Последнее место, где приходит карта стека.
Дело в том, что байт-код сам по себе не содержит явной информации о типе. Типы определяются неявно через анализ потока данных. Например, инструкция iconst создает целочисленное значение. Если вы храните его в слоте 1, этот слот теперь имеет int. Если поток управления сливается из кода, в котором вместо этого хранится число с плавающей запятой, слот теперь считается недопустимым типом, что означает, что вы не можете ничего сделать с этим значением, пока не перезапишите его.
Исторически верификатор байт-кода выводил все типы, используя эти правила потока данных. К сожалению, невозможно вывести все типы за один линейный проход через байт-код, потому что обратный переход может сделать недействительными уже выведенные типы. Классический верификатор решил эту проблему, перебирая код до тех пор, пока все не перестало меняться, что может потребовать нескольких проходов.
Однако проверка замедляет загрузку классов в Java. Oracle решила решить эту проблему, добавив новый, более быстрый верификатор, который может проверять байт-код за один проход. Для этого им требовалось, чтобы все новые классы, начиная с Java 7 (с Java 6 в переходном состоянии), содержали метаданные об их типах, чтобы байт-код мог быть проверен за один проход. Поскольку сам формат байт-кода не может быть изменен, эта информация о типе хранится отдельно в атрибуте, называемом StackMapTable
,
Простое хранение типа для каждого отдельного значения в каждой точке кода, очевидно, займет много места и будет очень расточительным. Чтобы сделать метаданные меньшими и более эффективными, они решили, что они должны перечислять только типы в позициях, которые являются целями прыжков. Если вы думаете об этом, это единственный раз, когда вам нужна дополнительная информация для проверки за один проход. Между целями прыжка весь поток управления является линейным, поэтому вы можете вывести типы между позициями, используя старые правила вывода.
Каждая позиция, в которой типы явно указаны, называется рамкой стековой карты. StackMapTable
Атрибут содержит список кадров по порядку, хотя они обычно выражаются как отличие от предыдущего кадра с целью уменьшения размера данных. Если в методе нет фреймов, что происходит, когда поток управления никогда не присоединяется (т. Е. CFG является деревом), то атрибут StackMapTable можно полностью опустить.
Так что это основная идея о том, как работает StackMapTable и почему он был добавлен. Последний вопрос - как создается неявный начальный кадр. Ответ, конечно, заключается в том, что в начале метода стек операнда пуст и слоты локальной переменной имеют типы, заданные типами параметров метода, которые определяются из дескриптора метода.
Если вы привыкли к Java, есть несколько небольших различий в том, как типы параметров метода работают на уровне байт-кода. Во-первых, виртуальные методы имеют неявный this
в качестве первого параметра. Во-вторых, boolean
, byte
, char
, а также short
не существует на уровне байт-кода. Вместо этого все они реализованы как целые объекты за кулисами.