Спецификация виртуальной машины


Чтобы посмотреть этот PDF файл с форматированием и разметкой, скачайте его и откройте на своем компьютере.
Персональные инструменты
Пространства имён
Представление объектов
Арифметика чисел с плавающей точкой
Арифметика чисел с плавающей точкой виртуальной машины Java и стандарт IEEE 754
Режимы работы с плавающей точкой
Правила преобразования множества значений
Специальные методы
Обзор инструкций
Типы данных и виртуальная машина Java
Инструкции загрузки и считывания
Арифметические инструкции
Инструкции преобразования типов
Создание и работа с объектами
Инструкции для работы со стеком операндов
Инструкции передачи управления
Вызов методов и инструкции возврата
Формирование исключений
Библиотека классов
Открытый дизайн, закрытая реализация
ГЛАВА 3. Компиляция программ в код виртуальной машины Java
Формат примеров
Использование констант, локальных переменных и управляющих структур
Доступ к константному пулу времени выполнения
Передача управления
Получение аргументов
Вызов методов
Работа с экземплярами класса
Компилирование операторов switch
Операции со стеком операндов
Генерация и обработка исключений
Компиляция инструкции finally
Компиляция инструкций синхронизации
ГЛАВА 4. Формат class-файла
Структура ClassFile
Внутренняя форма имён
Имена двоичных классов и интерфейсов
Сокращенная форма имен
Дескрипторы и сигнатуры
Грамматика обозначений дескрипторов и сигнатур
Дескрипторы поля
Дескрипторы методов
Константный пул
Структура CONSTANT_Class_info
Структуры CONSTANT_Fieldref_info, CONSTANT_Methodref_info и
Структурные ограничения
Проверка class-файлов
Проверка сравнением типов
Иерархия типов
Правила подтипов
Правила проверки типов
Средства доступа
Абстрактные методы и методы Native
Проверка кода
Комбинирование потоков стековых соответствий и инструкций
Обработка исключений
Изоморфные инструкции
Манипулирование стеком операндов
Инструкции загрузки
Инструкции сохранения
Список инструкций
Проверка по типам интерфейса
Процесс проверки по типам интерфейса
Верификатор байткода
Значения с типами long и double
Методы, инициализирующие экземпляр, и только что созданные объекты
Исключения и блок finally
Ограничения виртуальной машины Java
ГЛАВА 5. Загрузка, компоновка и инициализация
Константный пул времени выполнения
Запуск виртуальной машины
Создание и загрузка
Загрузка с помощью начального загрузчика классов
Загрузка с помощью пользовательского загрузчика классов
Создание массивов
Ограничения загрузки
Создание класса на основе данных class-файла
Разрешение классов и интерфейсов
Разрешение поля
Разрешение метода
Разрешение метода интерфейса
Разрешение типов методов и обработчиков методов
Разрешение спецификатора узла вызова
Управление доступом
Замещение методов
Связывание платформенно зависимых методов
Завершение работы виртуальной машины
ГЛАВА 6. Набор инструкций виртуальной машины Java
Допущения: значение слова «обязательный»
Зарезервированные коды операций
Ошибки виртуальной машины
Формат описания инструкций
ГЛАВА 7 Таблица инструкций по возрастанию их байт-кодов
Предисловие к первому изданию
Данная спецификация виртуальной машины Java написана для полного документирования
архитектуры виртуальной машины Java. Она важна для разработчиков компиляторов, которые
проектируют виртуальную машину Java и для программистов, реализующих совместимую виртуальную
машину Java.
Виртуальная машина Java является абстрактной машиной.
Ссылки на виртуальную машину Java
данной спецификации обращены к абстрактной машине, а не к реализации от компании Oracle либо
любой другой конкретной реализации. Данная спецификация служит документом для конкретной
реализации виртуальной машины Java только как чертёж, служащий документом для постройки дома.
Реализация виртуальной машины Java (также известная как интерпретатор времени выполнения)
должна воплощать в себе данную спецификацию, но ограничения на реализацию накладываются
только там, где это действительно необходимо.
Виртуальная машина Java, описанная здесь совместима с Java Platform
, Standard Edition 7 и
поддерживает язык программирования Java, описанный в
Спецификации языка Java, Java SE 7 Edition
Мы намеревались так написать эту спецификацию, чтобы предоставить полную информацию о
виртуальной машине Java и сделать возможным появление других полностью совместимых между
собой реализаций. Если вы задумали создать свою собственную реализацию виртуальной машины
Java, без колебаний обращайтесь к авторам спецификации за дополнительной информацией, чтобы
получить на 100% совместимую реализацию.
Виртуальная машина, ставшая затем виртуальной машиной Java первоначально была разработана
Джеймсом Гослингом (James Gosling) в 1992 году для поддержки языка программирования Oak. В
развитии проекта к его существующему состоянию прямо или косвенно принимали участие множество
людей, будучи в самых различных проектах и группах: проект Green компании Sun, проект FirstPerson,
Inc., проект LiveOak, группа Java Products Group, группа JavaSoft и в настоящее время Java Platform
Group компании Oracle. Авторы благодарны многим разработчикам, писавшим код и оказывавшим
техническую поддержку.
Эта книга берет своё начало в качестве внутреннего проекта по документации. Кейти Волрат (Kathy
Walrath) выполнила редактирование начального черновика, помогая тем самым миру увидеть первое
описание внутренней реализации языка программирования Java. В то же время Марией Кемпион
(Mary Campione) описание было переведено в формат HTML и опубликовано на нашем веб-сайте,
прежде чем оно было расширено до размеров книги.
Создание
Спецификации виртуальной машины Java
во многом обязано поддержке группы Java
Products Group, руководимой главным менеджером Рутом Хеннигаром (Ruth Hennigar), а также усилиям
редакторов Лизи Френдли (Lisa Friendly) и Майка Хендриксона (Mike Hendrickson) и его группы из
Addison-Wesley. Безмерно помогли поднять качество издания множественные замечания и
предложения, полученные как от редакторов рукописи так и читтателей уже опубликованной книги.
Мы особенно благодарим Ричарда Така (Richard Tuck) за его тщательное прочтение и правку
рукописи. Отдельное спасибо Билу Джою (Bill Joy), чьи комментарии, правки и помощь во многом
способствовали целостности и точности данной книги.
Тим Лидхольм (Tim Lindholm)
Френк Йеллин (Frank Yellin)
Предисловие ко второму изданию
Во второй редакции спецификации виртуальной машины Java добавлены изменения касающиеся
выхода платформы Java® 2, версии 1.2. Вторая редакция также включает в себя множественные
правки и разъяснения касательно изложения спецификации, оставив при этом логическую часть
спецификации без изменения. Мы попытались исправить опечатки а также откорректировать список
опечаток (надеемся без привнесения новых опечаток) и добавить больше деталей в описании в
случаях неясности или двусмысленности. В частности, мы исправили определённое число
несоответствий между
Спецификацией виртуальной машины Java
и
Спецификацией языка Java
Мы благодарны многим читателям, которые с усиленным вниманием прочли первую редакцию данной
книги и высветили для нас ряд проблем. Некоторые лица и группы заслуживают отдельной
благодарности за то, что обратили наше внимание на проблемные места в спецификации либо
непосредственно способствовали написанию нового материала:
Клара Шроер (Carla Schroer) и её команда тестировщиков совместимости в Купертино, Калифорния и
Новосибирске, Россия (с особенной благодарностью Леониду Арбузову и Алексею Кайгородову) с
особым усердием написали тесты совместимости для каждого тестируемого утверждения в первой
редакции. В процессе работы они нашили множество мест, где исходная спецификация была либо не
ясна либо неполна.
Джероин Вермулен (Jeroen Vermeulen), Дженис Шеперд (Janice Shepherd), Роли Перера (Roly Perera),
Джо Дарси (Joe Darcy) и Сандра Лузмор (Sandra Loosemore) добавили множество комментариев и
ценных замечаний, которые улучшили данное издание.
Мэрилин Рэш (Marilyn Rash) и Хилари Селби Полк (Hilary Selby Polk) из редакции Addison Wesley
Longman помогли нам улучшить читаемость и макет страниц в данном издании, в то время как мы
были заняты технической частью спецификации.
Особую благодарность мы направляем Гиладу Брача (Gilad Bracha), выведшему строгость изложения
на принципиально новый уровень и добавившему большой объем нового материала, особенно в
главах 4 и 5. Его преданность «компьютерной теологии» и несгибаемое чувство долга в отношении
устранения несоответствий между
Спецификацией виртуальной машины Java
и
Спецификацией языка
Java
позволили невообразимо улучшить качество данной книги.
Тим Лидхольм (Tim Lindholm)
Френк Йеллин (Frank Yellin)
Предисловие к изданию Java SE 7
Издание Java SE 7
Спецификации виртуальной машины Java
включает в себя все изменения,
сделанные со времени выхода второго издания в 1999 году. В дополнение к этому было сделано
множество правок и разъяснений, согласовывающих спецификацию со многими известными
реализациями виртуальной машины Java, а также с принципами, общими для виртуальной машины
Java и языка программирования Java.
Разработка платформы Java SE 5.0 в 2004 году привела к множественным изменениям в языке
программирования Java, но имела относительно не большое влияние на архитектуру виртуальной
машины Java. Изменения были выполнены в формате
файла для поддержки нового
функционала в языке программирования Java, такого как обобщённые типы и методы с переменным
числом параметров.
Появление платформы Java SE 6 в 2006 году не повлияло непосредственно на язык программирования
Java, но привело к созданию нового подхода в проверке байткода виртуальной машины Java. Ева Роуз
(Eva Rose) в своей кандидатской диссертации выполнила радикальный пересмотр верификации
байткода JVM в контексте платформы Java Card
. Это, во-первых, привело к реализации Java ME
CLDC и в конце концов пересмотру процесса проверки для Java SE, описанного в главе 4.
Шень Лиань (Sheng Liang) выполнила реализацию верификатора для Java ME CLDC. Антеро
Тайвалсаари (Antero Taivalsaari) руководил разработкой спецификации Java ME CLDC в целом, а Гилад
Брача (Gilad Bracha) был ответственен за документацию по верификатору. Анализ проверки байткода
JVM, выполненный Алессандро Коглио (Alessandro Coglio), был самой трудоёмкой, наиболее
соответствующей действительности и счерпывающей тему новой частью, добавленной в
спецификацию. Вей Тао (Wei Tao) совместно с Фрэнком Йеллиным (Frank Yellin), Тимом Линдхольмом
(Tim Lindholm) и Гиладом Брача написали Пролог верификатор, лёгшим в основу как спецификации
Java ME так и Java SE. Затем Вей реализовал спецификацию в реальном коде «для настоящей»
HotSpot JVM. Затем Мингайо Янг (Mingyao Yang) улучшил архитектуру и саму спецификацию и
реализовал итоговую версию, которая превратилась в реализацию ссылок в Java SE 6. Спецификация
во многом была улучшена благодаря усилиям группы JSR 202 Expert Group: Питера Бурки (Peter
Burka), Алессандро Коглио (Alessandro Coglio), Сеньхун Джина (Sanghoon Jin), Кристиана Кемпера
(Christian Kemper), Лэри Ро (Larry Rau), Эви Роуз (Eva Rose), Марка Штольца (Mark Stolz).
Вышедшая в 2011 году платформа Java SE 7 реализовала, данное в 1997 году в
виртуальной машины Java
, обещание: «В будущем, мы добавим в виртуальную машину Java новые
расширения для того чтобы представить улучшенную поддержку других языков». Гилад Брача в своей
работе по динамической замене типов предвидел трудности реализации статической системы типов
виртуальной машины Java в динамически типизированных языках. В результате джоном Роузом (John
Rose) и экспертной группой JSR 292 Expert Group (Оля Бини (Ola Bini), Реми Форакс (Rémi Forax), Дэн
Хейдинга (Dan Heidinga), Фредрик Орштром (Fredrik Öhrström), Джочен Теодору (Jochen Theodorou), а
также Чарли Наттер (Charlie Nutter) и Кристиан Тайлингер (Christian Thalinger)) была разработана
инструкция
invokedynamic
и вся необходимая инфрастуктура.
Множество людей, которых мы не упомянули в данном предисловии, внесли свою лепту в архитектуру
и реализацию виртуальной машины Java. Превосходная производительность JVM, которую мы видим
сегодня, была бы не достижима без технологического фундамента, заложенного Девидом Унгаром
(David Ungar) и его коллегами из проекта Self компании Sun Labs. Эта технология пришла извилистый
путь из проекта Self через проект Animorphic Smalltalk VM и затем, в конце концов, стала Oracle
HotSpot JVM. Ларс Бак (Lars Bak) и Урс Хёльзль (Urs Hölzle) присутствовали при всех перипетиях
технологии и более чем кто-либо другой ответственны за высокую производительность присущую JVM
в наши дни.
Эта спецификация был значительно улучшена благодаря усилиям следующих людей: Мартин Бакхольц
(Martin Buchholz), Брайан Гоэц (Brian Goetz), Пол Хоэнси (Paul Hohensee), Девид Холмс (David Holmes),
Карен Киннер (Karen Kinnear), Кейт МакГайген (Keith McGuigan), Джефф Найзвонгер (Jeff Nisewanger),
Марк Рейнхольд (Mark Reinhold), Наото Сато (Naoto Sato), Билл Паф (Bill Pugh), а также Уди
Даниконда (Uday Dhanikonda), Дженет Коэниг (Janet Koenig), Адам Месингер (Adam Messinger), Джон
Пэмпач (John Pampuch), Джоржд Сааб (Georges Saab) и Бернард Траверсет (Bernard Traversat). Джон
Картни (Jon Courtney) и Роджер Ригз (Roger Riggs) помогли гарантировать, что данная спецификация
применима как к Java ME так и к Java SE. Леонид Арбузов, Станислав Авзан, Юрий Гаевский, Илья
Мухин, Сергей Резник и Кирилл Широков выполнили потрясающий объем работ в Java Compatibility Kit
(набор тестов по проверки совместимости версий) для того чтобы гарантировать корректность данной
Гилад Брача (Gilad Bracha)
Алекс Бакли (Alex Buckley)
Java Platform Group, Oracle
Язык программирования Java это многоцелевой, многопоточный объектно-ориентированный язык. Его
синтаксис похож на C и C++ но исключает некоторые особенности, которые делают на C и C++
сложным, запутанным и небезопасным. Первоначально платформа Java была разработана для
решения проблем построения программного обеспечения для сетевых устройств. Она была
спроектирована для архитектур, включающих в себя множество серверов, при этом позволяя
безопасно обновлять компоненты ПО. Чтобы удовлетворить этим требованиям, скомпилированный код
должен быть выполняемым на любом клиенте, а также гарантировать безопасность своей работы.
Развитие мировой паутины сделало эти требования более значимыми. Современные веб-браузеры
позволяют миллионам людей путешествовать по сети и легко получать доступ практически к любым
данным. В конце концов, была создана медиа среда, в которой то, что видит и слышит пользователь,
совершенно не зависит ни от типа компьютера, который он использует, ни от скорости сетевого
соединения: быстрого либо медленного.
Однако активные пользователи сети вскоре обнаружили, что формат документов HTML слишком
ограничен. HTML расширения, такие как формы, только подчеркнули существующие ограничения;
стало ясно, что ни один браузер не в состоянии предоставить все инструменты, которые пользователи
желают видеть. Выход из тупика был в расширяемости. Первый браузер HotJava компании Sun
продемонстрировал некоторые интересные свойства языка программирования и платформы Java, дав
возможность внедрять программы внутрь HTML страниц. Программы загружались непосредственно в
браузер параллельно с HTML страницами, в которых они появлялись. Прежде чем браузер давал
возможность выполнить программу, они проходили тщательную проверку на безопасность. Также как
и HTML страницы, скомпилированные программы не зависят от протоколов сети и типов машин, на
которых они выполняются. Программы ведут себя одинаково вне зависимости от того, где они были
созданы и куда загружены.
Веб-браузер, поддерживающий платформу Java, теперь не был ограничен заранее определенным
набором возможностей. Посетители веб страниц, имеющих динамическое содержимое, могли быть
уверены, что их система надёжно защищена. В тоже время программисты получили возможность,
однажды написав программу, запускать её на любом компьютере, поддерживающем платформу Java.
Виртуальная машина Java является ключевым аспектом платформы Java. Это компонент технологии,
который отвечает за независимость от программного обеспечения и операционной системы,
небольшой размер скомпилированного кода и возможность защитить пользователей от вредоносных
ГЛАВА 1. Введение
Немного истории
Виртуальная машина Java
Виртуальная машина Java это абстрактная вычислительная машина. Как и реальная вычислительная
машина, она имеет набор инструкций и манипулирует разными участками памяти во время своей
работы. Вообще говоря, это достаточно общий подход - реализовать язык программирования,
используя виртуальную машину; наиболее известная среди таких машин – машина P-Code,
реализованная в Университете Калифорнии, в Сан Диего.
Первый прототип реализации виртуальной машины Java был сделан компанией Sun Microsystems, Inc.
на ручном устройстве, которое напоминало современный персональный цифровой помощник
(миникомпьютер с записной книжкой, календарём и местом для хранения информации. В данный
момент полностью вытеснены смартфонами -
прим. перев.
). В настоящее время компания Oracle
создала виртуальные машины Java для мобильных устройств, настольных компьютеров и серверных
систем, однако сама виртуальная машина не подразумевает привязки к конкретному оборудованию,
операционной системе или способу ее реализации. Виртуальная машина Java может быть реализована
как компилированием ее инструкций в набор команд конкретного процессора, так и непосредственно
в процессоре.
Непосредственно виртуальная машина Java «не знает» ничего о языке программирования Java, ей
лишь известен заданный формат двоичных файлов – файлов, имеющих расширение
. Эти
файлы содержат инструкции виртуальной машины (байткод), таблицы символов и другую
вспомогательную информацию. Из соображений безопасности виртуальная машина Java предъявляет
строгие синтаксические и структурные требования на код, расположенный в
файле. Тем не
менее, любой язык, функциональность которого может быть выражена в средствами корректного
файла, может быть интерпретирован для виртуальной машины Java. Привлечённые
общедоступностью и платформенной независимостью, разработчики компиляторов других языков
могут использовать виртуальную машину как удобную платформу для своих реализаций.
Эта книга структурирована следующим образом:
Глава 2
содержит обзор архитектуры виртуальной машины Java.
Глава 3
описывает принципы компиляции кода, написанного на языке программирования Java в
набор инструкций виртуальной машины Java.
Глава 4
содержит описание формата
файла – формата, не зависящего от аппаратного
обеспечения и операционной системы – который используется для хранения скомпилированных
классов и интерфейсов.
Глава 5
описывает запуск виртуальной машины Java, а также загрузку, компоновку и
инициализацию классов и интерфейсов.
Краткое содержание глав
Глава 6
определяет набор инструкций виртуальной машины Java. Инструкции расположены в
алфавитном порядке их мнемонических записей.
Глава 7
содержит таблицу инструкций виртуальной машины Java, расположенных по возрастанию
их байт-кодов.
Глава 2
Спецификации виртуальной машины Java (второе издание)
содержит обзор языка
программирования Java; этот обзор выполнен для описания работы виртуальной машины Java и не
является частью спецификации. В
Спецификации виртуальной машины Java (второе издание)
читатель отсылается к Спецификации языка программирования Java SE 7 Edition за более подробной
информацией о языке программирования Java. Такая ссылка имеет вид: (см. JLS §x.y).
Глава 8
Спецификации виртуальной машины Java (второе издание)
посвящена низкоуровневым
операциям взаимодействия потоков виртуальной машины Java с основной разделяемой памятью. Эта
глава была переделана из главы 17 первой редакции
Спецификации языка программирования Java
Глава 17
Спецификации языка программирования Java SE 7 Edition
отражает
Спецификацию модели
памяти и потоков
, составленную экспертной группой JSR-133. За информацией о блокировках и
потоках читатель отсылается к соответствующим главам
Спецификации модели памяти и потоков
Везде в этой книге мы имеет дело только с классами и интерфейсами из Java SE API. Везде, где мы
ссылаемся на класс или интерфейс с помощью идентификатора
, на самом деле мы подразумеваем
следующую ссылку
. Для классов не из пакета
мы используем полное имя
(имя пакета и имя класса). Везде, где мы ссылаемся на класс или интерфейс, объявленный в пакете
или любом из его подпакетов, мы имеем в виду, что класс или интерфейс загружен загрузчиком
классов (см.
Везде, где мы ссылаемся на подпакет объявленный в пакете
, мы имеем в виду, что класс или
интерфейс загружен загрузчиком классов. В спецификации используются следующие виды шрифтов:
Моноширинный шрифт
используется для примеров исходного кода на языке программирования
Java, типов данных виртуальной машины Java, исключений и ошибок.
Курси
в используется для обозначения «языка ассемблера» виртуальной машины Java: операторов,
операндов и элементов данный в области данный времени выполнения виртуальной машины Java.
Курсив также используется для введения новых терминов и для обозначения акцента в
Принятые обозначения
Этот документ посвящён абстрактной виртуальной машине, он не описывает конкретную реализацию
виртуальной машины. Для корректной реализации виртуальной машины Java, разработчику
необходимо только правильно прочесть
файл и правильно выполнить операции, определённые
там. Детали имплементации не являются частью спецификации виртуальной машины Java, и
приведение их неоправданно ограничило бы свободу разработчика. Например, распределение памяти
во время работы программы, алгоритм сборщика мусора и внутренняя оптимизация инструкций
виртуальной машины Java (например, перевод их в машинный код) оставлены на усмотрение
Все ссылки относительно кодовой таблицы Unicode в этом документе приведены в соответствии со
стандартом Unicode версии 6.0.0 доступной по адресу
Скомпилированный для выполнения виртуальной машиной Java код, представляет собой набор данных
двоичного формата независимого от операционной системы и аппаратного обеспечения, обычно (но
не всегда) хранимый в файле, известном как
файл. Формат
файла точно определяет
представление класса или интерфейса, включая такие особенности как порядок байтов при работе с
двоичными данными в платформенно зависимом файле.
В главе 4, «Формат class файла» приведено подробное описание формата.
Так же как и язык программирования Java виртуальная машина Java оперирует двумя
разновидностями типов данных: примитивные типы и ссылочные типы. Соответственно существует
две разновидности значений, которые могут храниться в переменных, быть переданы как аргументы,
возвращены методами и использованными в операторах: примитивные значения и ссылочные
Виртуальная машина Java полагает, что почти все проверки соответствия типов будут выполнены до
запуска кода (обычно компилятором) и поэтому такую проверку типов не делает. Нет необходимости
помечать значения примитивных типов или как-нибудь иначе наблюдать за ними во время
ГЛАВА 2. Структура виртуальной машины Java
файла
Типы данных
выполнения программы, также нет необходимости отличать примитивные типы от ссылочных типов.
Вместо этого, виртуальная машина Java, содержит наборы инструкций предназначенных для
выполнения операций со строго определёнными типами данных. Например, следующие команды
iadd
ladd
,
, и
представляют собой весь спектр команд для сложения двух числовых значений и
получения одного результата, однако каждая предназначена для операндов строго определённого
типа:
,
,
, и
соответственно. Более подробно описание поддерживаемых типов
изложено в § 2.11.1.
Виртуальная машина Java содержит явную поддержку объектов. Объектом мы называем динамически
создаваемый экземпляр класса или массив. Ссылка на объект представлена в виртуальной машине
Java типом
. Значения типа
могут быть рассмотрены как указатели на объекты.
На один и тот же объкт может существовать множество ссылок. Передача объектов, операции над
объектами, проверка объектов происходит всегда посредством типа
Виртуальная машина Java поддерживает следующие примитивные типы: числовые типы,
(см. § 2.3.4) и
, содержит 8-битовые знаковые целые. Значение по умолчанию - ноль.
, содержит 16-битовые знаковые целые. Значение по умолчанию - ноль.
, содержит 32-битовые знаковые целые. Значение по умолчанию - ноль.
, содержит 64-битовые знаковые целые. Значение по умолчанию - ноль.
, содержит 16-битовые беззнаковые целые, представляющие собой кодовые точки таблицы
символов Unicode в базовой странице UTF-16. Значение по умолчанию - нулевая кодовая точка
Типы с плавающей точкой:
, содержит числа с плавающей точкой одинарной точности. Значение по умолчанию -
положительный ноль.
, содержит числа с плавающей точкой двойной точности. Значение по умолчанию -
положительный ноль.
Значение
типа может быть
или
, значение по умолчанию
.
Примечание. В первой редакции спецификации виртуальной машины Java тип данных
не
рассматривался как машинный тип. Однако
значения частично поддерживались виртуальной
машиной. Во второй редакции эта неясность была устранена, и тип данных
стал машинным
Примитивные типы и значения
Тип данных
для типа
от -128 до 127 (-2
до 2
- 1) включительно;
для типа
от -32768 до 32767 (-2
до 2
- 1) включительно;
для типа
от -2147483648 до 2147483647 (-2
до 2
- 1) включительно;
для типа
от -9223372036854775808 до 9223372036854775807 (-2
до 2
- 1) включительно;
для типа
от 0 до 65535 включительно;
Типами данных с плавающей точкой являются типы
и
соответственно 32-х битые
значения одинарной точности и 64-х битные значения двойной точности. Формат чисел и операции
над ними соответствуют спецификации
Целочисленные типы и их значения
Типы данных с плавающей точкой, множества значений и значения
представлено в виде
, где
равно -1 либо 1,
– положительное целое меньше
чем 2
,
– целое число в пределах
= -(2
-1
-2) и
= 2
-1
-1 включительно,
и
параметры, зависящие от набора значений. Одно и то же числовое значение можно представить
несколькими способами. Например, предположим, что
– значение из набора, представимого в
указанной выше форме при определённых
,
и
; тогда, если
- чётно и
– меньше чем 2
-1
, то
чтобы получить новое представление значения
, можно
разделить на два, одновременно увеличив
на единицу. Представление

+ 1)
значения
называется нормализованным, если
-1
; в противном случае говорят, что представление денормализованное. Если значение из
множества значений не может быть представлено так, чтобы было справедливо неравенство
≥ 2
-1
то такое значение называют денормализованным значением, поскольку оно не имеет
нормализованного представления.
В таблице 2.1 приведены ограничения для параметров
и
(а также производных параметров
) для двух обязательных и двух не обязательных наборов значений чисел с плавающей точкой.
Таблица 2.1 – Параметры для множества чисел с плавающей точкой
Одинарная точность с
расширенной экспонентой
Двойная точность с
расширенной экспонентой
≥ 11
≥ 15
≥ + 1023
+ 1023
≥ + 16383
-126
≤ - 1022
-1022
≤ -16383
Значение константы
зависит от реализации виртуальной машины там, где наборы значений с
расширенной экспонентой вообще поддерживаются (в любом случае
принадлежит пределам,
указанным в таблице); в свою очередь константы
и
определяются в зависимости от
значения
Каждый из четырёх наборов значений содержит не только конечные ненулевые значения, описанные
выше, но также пять дополнительных значений: положительный ноль, отрицательный ноль,
положительная бесконечность, отрицательная бесконечность, и не-число (NaN).
Обратите внимание, что ограничения в таблице 2.1 разработаны таким образом, что каждый элемент
множества значений одинарной точности также принадлежит множеству значений одинарной
точности с расширенной экспонентой, множеству значений с двойной точностью и множеству
значений с двойной точностью с расширенной экспонентой. Каждый набор значений с расширенной
экспонентой имеет более широкие пределы значений экспоненты по сравнению со стандартным
набором, но точность чисел при этом одинаковая.
Элементы множества значений чисел с плавающей точкой одинарной точности соответствует
значениям, определённым в стандарте IEEE 754, за исключением того, что в этом множестве только
одно значение есть не-число NaN (стандарт IEEE 754 предусматривает 2
-2 различных не-чисел
NaN). Элементы множества значений чисел с плавающей точкой двойной точности соответствует
значениям, определённым в стандарте IEEE 754, за исключением того, что в этом множестве только
одно значение есть не-число NaN (стандарт IEEE 754 предусматривает 2
-2 различных не-чисел
NaN). Однако, обратите внимание, что элементы множества значений чисел с плавающей точкой с
расширенной экспонентой с одинарной и двойной точностью
не соответствуют
представленным в стандарте IEEE 754 расширенным форматом одинарной точности и расширенным
форматом двойной точности соответственно. Данная спецификация не регламентирует внутренне
представление чисел с плавающей точкой; единственное место, где должен быть соблюдён формат
чисел с плавающей точкой – это формат
Набор значений одинарной точности, набор значений двойной точности, набор значений одинарной
точности с расширенной экспонентой и набор значений двойной точности с расширенной экспонентой
не являются типами данных. В реализации виртуальной машины Java всегда допустимо использовать
набор значений одинарной точности для представления значения типа
; однако, в
определённом контексте также допустимо использовать набор значений одинарной точности с
расширенной экспонентой. Аналогично всегда допустимо использовать набор значений двойной
точности для представления значения типа
; однако, в определённом контексте также
допустимо использовать набор значений двойной точности с расширенной экспонентой.
Все значения (кроме не-чисел NaN) множества чисел с плавающей точкой
. Если числа
упорядочить по возрастанию, то они образуют такую последовательность: отрицательная
бесконечность, отрицательные конечные значения, отрицательный ноль, положительный ноль,
положительные значения и положительная бесконечность.
Сравнивая положительный и отрицательный ноль, мы получим верное равенство, однако существуют
операции, в которых их можно отличить; например, деля
на
, мы получим положительную
бесконечность, но деля
на
-0.0
мы получим отрицательную бесконечность.
Не-числа NaN
не упорядочены
, так что сравнение и проверка на равенство вернёт
, если хотя бы
один из операндов не-число NaN. В частности проверка на равенство значения самому себе вернёт
тогда и только тогда, кода операнд не-число NaN. Проверка на неравенство вернёт
когда хотя бы из операндов не-число NaN.
Тип
Тип
и его значения
Не смотря на то, что виртуальная машина Java поддерживает тип данных
, поддержка этого
типа весьма ограничена. Нет никаких инструкций виртуальной машины Java непосредственно
относящихся к работе со значениями типа
. Вместо этого все выражения, содержащие тип
данных
, сводятся к эквивалентным операциям с типом данных
Тем не менее, виртуальная машина Java поддерживает хранения массивов с булевским типом данных.
Операция
позволяет создавать массивы с булевым типом элементов. Доступ и модификация
таких массивов осуществляется инструкциями, предназначенными для работы с типом данных
, а
именно:
baload
и
.
Примечание. В реализации виртуальной машины Java компании Oracle, булевы значения массивов
кодируются массивом значений с типом
, 8 бит на один элемент массива типа
В виртуальной машине Java используется значение 1 для кодирования логического
и 0 для
. Везде, где компилятор преобразовывает булевы типы в тип
, он придерживается
указанного выше соглашения.
Существуют три разновидности ссылочных (
) типов: тип класса, тип массива и тип
интерфейса. Значения этих типов представляют собой ссылки на экземпляр класса, ссылки на массив
и ссылки на имплементацию интерфейса соответственно.
Тип массива представляет собой
составной тип
единичной размерности (длина которого не
определена типом). Каждый элемент составного типа сам по себе может также быть массивом.
Последовательно рассматривая иерархию составных типов в глубину, (тип, из которого состоит
составной тип, из которого состоит составной тип и т.д.) мы придём к типу, который не является
массивом; он называется
элементарным типом
типа массив. Элементарный тип всегда либо
примитивный тип, либо тип класса, либо тип интерфейса.
Тип
может принимать специальное нулевое значение, так называемая ссылка на не
существующий объект, которое обозначается как
. Значение
изначально не принадлежит
ни к одному из ссылочных типов и может быть преобразовано к любому.
Спецификация виртуальной машины Java допускает использование произвольной константы для
кодирования значения
Тип
Ссылочные типы и их значения
Во время выполнения программы виртуальная машина Java использует разные области для хранения
данных. Некоторые из этих областей хранения данных создаются при запуске виртуальной машины
Java и освобождаются при завершении работы виртуальной машины. Другие области хранения
данных принадлежат потоку. Связанные с потоком области данных создаются при создании потока и
освобождаются при завершении работы потока.
Виртуальная машина Java может поддерживать множество потоков, выполняющихся одновременно.
Каждый поток виртуальной машины Java имеет свой регистр
(program counter (англ.) –
программный счётчик. –
прим. перев.
). В каждый момент времени каждый поток виртуальной машины
исполняет код только одного метода, который называется текущим методом для данного потока. Если
метод платформенно независимый (т.е. в объявлении метода не использовано ключевое слово
) регистр
содержит адрес выполняющейся в данный момент инструкции виртуальной
машины Java. Если метод платформенно зависимый (
метод) значение регистра
определено. Разрядность регистра
достаточная, чтобы хранить значения типа
Области данных времени выполнения
Регистр
Стек виртуальной машины Java
Примечание. В первой редакции данной спецификации стек виртуальной машины Java назывался просто
стеком Java
Спецификация позволяет реализовать стек виртуальной машины Java фиксированного размера либо
динамического размера: расширяемого и сужаемого по мере работы. Если стек виртуальной машины
Java фиксированного размера, то размер стека для каждого потока может быть выбран независимо в
момент создания стека.
Примечание. Реализация виртуальной машины Java может позволить разработчику или пользователю
управлять тачальным размером стека виртуальной машины, так же как и в случае динамически
изменяемого размера, виртуальная машина позволяет задать минимальное и максимальное значение
размера стека.
В следующих случаях виртуальная машина Java формирует исключение при работе со стеком:
Если вычисления в потоке требуют памяти более чем позволено размером стека, виртуальная
машина Java формирует исключение
Если стек виртуальной машины Java допускает динамическое увеличение размера и попытка
такого увеличения была выполнена, однако вследствие нехватки памяти не завершена успешно
либо не достаточно памяти при инициализации стека при создании потока, то виртуальная машина
Java формирует исключение
Виртуальная машина Java содержит область памяти, называемую
, которая находится в
пользовании всех потоков виртуальной машины. Куча – это область памяти времени выполнения,
содержащая массивы и экземпляры всех классов.
Куча создаётся при запуске виртуальной машины Java. Удаление неиспользуемых объектов в куче
производится системой автоматического управления памятью (известной как
сборщик мусора
объекты никогда не удаляются явно. Виртуальная машина Java не предполагает какого-либо одного
алгоритма для системы автоматического управления памятью; алгоритм может быть произвольно
задан разработчиком виртуальной машины в зависимости от системных требований. Куча может быть
фиксированного размера, либо динамически расширяться и сужаться при удалении объектов. Участок
памяти для кучи виртуальной машины Java не обязательно должен быть непрерывным.
Примечание. Реализация виртуальной машины Java позволяет разработчику или пользователю управлять
начальным размером кучи, так же как и в случае динамически изменяемого размера, виртуальная
машина позволяет задать минимальное и максимальное значение размера кучи.
В следующих случаях виртуальная машина Java формирует исключение при работе с кучей:
Если вычисления требуют памяти более, чем может выделить автоматическая система управления
памятью, виртуальная машина Java формирует исключение
Виртуальная машина Java содержит область памяти, называемую
областью методов
, которая
находится в пользовании всех потоков виртуальной машины. Область методов аналогична хранилищу
скомпилированного кода в традиционных языках программирования или области памяти «текстовый
сегмент» в процессе операционной системы. Область методов хранит принадлежащие классам
структуры, такие как хранилище констант (константный пул), данные полей и методов, код методов и
конструктор включая специальные методы, а также код инициализации экземпляров и интерфейсов.
Область методов создаётся при запуске виртуальной машины. Хотя область методов логически
принадлежит куче, в простых реализациях виртуальной машины допустимо не сокращать область
методов и не использовать для нее сборщик мусора. Эта спецификация виртуальной машины Java не
задаёт однозначного расположения или политики управления памятью для скомпилированного кода.
Область методов может быть фиксированного размера, либо динамически расширяться и сужаться
при необходимости. Участок памяти для области методов виртуальной машины Java не обязательно
должен быть непрерывным.
Примечание. Реализация виртуальной машины Java может позволить разработчику или пользователю
управлять начальным размером области методов, так же как и в случае динамически изменяемого
размера, виртуальная машина позволяет задать минимальное и максимальное значение размера области
В следующих случаях виртуальная машина Java формирует исключение при работе с областью
Если работа программы требуют памяти более, чем может выделить автоматическая система
управления памятью, виртуальная машина Java формирует исключение
Область методов
Хранилище констант времени выполнения это связанное с классом или интерфейсом представления
времени выполнения таблицы
файла
. Представление содержит несколько
разновидностей констант, начиная от числовых литералов, известных на этапе компиляции до ссылок
в членах-данных класса и методах, разрешить которые необходимо во время выполнения. Хранилище
констант времени выполнения выполняет ту же функцию, что и таблица символов в традиционных
языках программирования, хотя хранилище и содержит данные в гораздо более широком диапазоне,
чем просто символьная таблица.
Каждое хранилище констант времени выполнения расположено в области методов виртуальной
машины Java. Хранилище констант времени выполнения класса или интерфейса создаётся, когда
класс или интерфейс создаётся виртуальной машиной Java.
В следующих случаях виртуальная машина Java формирует исключение при работе с хранилище
констант времени выполнения:
Если при создании класса или интерфейса хранилище констант времени выполнения требуют
памяти более, чем доступно для виртуальной машины Java, виртуальная машина Java формирует
исключение
Примечание. Более подробную информацию по созданию хранилища констант времени выполнения см. в
главе 5.
Реализация виртуальной машины Java позволяет использовать традиционные стеки, коротко
называемые "С стеками" (от англ. conventional - традиционный -
прим. перев.
) для поддержки
методов (методов, написанных на языках, отличных от Java). Те реализации виртуальной машины
Java, которые не могут загружать
методы и таким образом не используют традиционные
стеки, не нуждаются также и в поддержке стеков
Эта спецификация позволяет стекам
методов быть фиксированного размера, либо
динамически расширяться и сужаться при необходимости. Если стеки
фиксированного размера, то их размер задаётся в момент создания стека и не зависит от размеров
других стеков.
Примечание. Реализация виртуальной машины Java позволяет разработчику или пользователю управлять
Хранилище констант времени выполнения (константный пул)
Стеки Native методов
начальным размером стеков
методов в случае их фиксированного размера так же, как и в случае
динамически изменяемого размера, виртуальная машина позволяет задать минимальное и максимальное
значение размера стеков
методов.
В следующих случаях виртуальная машина Java формирует исключение при работе со стеками
Если вычисления в потоке требуют памяти более чем позволено размером стека
виртуальная машина Java формирует исключение
Если стек виртуальной машины Java допускает динамическое увеличение размера и попытка
такого увеличения была выполнена, однако вследствие нехватки памяти не завершена успешно
либо не достаточно памяти при инициализации стека native методов при создании потока, то
виртуальная машина Java формирует исключение
Фреймы
используются как для хранения данных и промежуточных результатов так и для организации
динамического связывания, возвращения значений из методов и управления исключениями.
Новый фрейм создаётся, когда происходит вызов метода. Фрейм уничтожается, когда вызов метода
завершён вне зависимости от того было или завершение метода успешным или аварийным (метод
выбросил не перехваченное исключение). Фреймы хранятся в стеке потока виртуальной машины Java,
создающего эти фреймы. Каждый фрейм содержит свой массив локальных переменных (см.
свой стек операндов (см.
), ссылку на хранилище констант времени выполнения (см.
текущего класса текущего метода.
Примечание. Дополнительно во фрейме может храниться информация специфическая для каждой
реализации виртуальной машины, например, отладочная информация.
Размер массива локальных переменных и стека операндов определяется во время компиляции и
хранится вместе с кодом метода, связанного с фреймом. Поэтому размер структур данных, хранящихся
во фрейме, зависит только от реализации виртуальной машины Java; память для этих структур
выделяется одновременно с вызовом метода.
Только один фрейм активен в каждый момент времени для каждого потока - фрейм, исполняемого в
данный момент метода. Такой фрейм называется
текущим фреймом
, а метод -
текущим методом
Класс, которому принадлежит текущий метод, называется
текущим классом
Операции над локальными переменными и операнды в стеке обычно ссылаются на текущий фрейм.
Фрейм перестаёт быть текущим, если связанный с ним метод вызывает другой метод или текущий
метод завершает своё выполнение. Когда метод вызывается, то создаётся новый фрейм, который
становится текущим при передаче управления вызываемому методу. Когда метод завершает своё
выполнение, текущий фрейм передаёт результаты выполнения (если таковые имеются) предыдущему
фрейму. После этого текущий фрейм уничтожается, а предыдущий фрейм становится текущим.
Обратите внимание, что фрейм, созданный потоком, виден только потоку-владельцу и для других
потоков не доступен.
Каждый фрейм содержит массив переменных, известных как
локальные переменные
. Длина массива
локальных переменных каждого фрейма определяется на этапе компиляции; массив хранится в
двоичном представлении класса или интерфейса совместно с кодом метода, связанного с фреймом.
Каждая локальная переменная может содержать значения следующих типов:
,
,
,
,
,
, или
Локальные переменные
Каждый фрейм содержит стек операндов, организованный по принципу «последним пришёл, первым
ушёл» (анг. LIFO, last-in-first-out –
прим. перев.
) Максимальная глубина стека операндов определяется
во время компиляции; значение глубины хранится совместно с кодом, связанным с фреймом (см.
). Там где это ясно из контекста, мы иногда будем называть стек операндов текущего фрейма
просто стеком операндов.
Сразу после создания фрейма стек операндов советующего фрейма пуст. Виртуальная машина Java
предоставляет инструкции загрузки констант или значений из локальных переменных или полей в
стек операндов. Другие инструкции виртуальной машины Java получают операнды из стека,
обрабатывают их, и записывают результат обратно в стек операндов. Стек операндов также
используется для подготовки параметров для передачи в методы и получения результатов
выполнения метода.
Например, инструкция
iadd
складывает два значения типа
. Для ее работы необходимо, чтобы два
значения типа
, записанные предыдущими инструкциями, были на вершине стека операндов. Два
значения типа
считываются из стека операндов. Они складываются, и их сумма записывается
обратно в стек операндов. Промежуточные вычисления могут храниться в стеке операндов и
использоваться в последующих вычислений.
Каждый элемент стека операндов может хранить все значения из списка поддерживаемых
виртуальной машиной Java типов, включая
и
Все операции со значениями стека операндов нужно проводить в соответствии с их типом.
Невозможно, к примеру, поместить два значения типа
работать с ними как с типом
поместить два значения типа
и сложить их инструкцией
iadd
. Небольшое количество
инструкций виртуальной машины Java (таких как
или
) работают с данными времени
выполнения как с «сырыми» значениями без учета их типов; эти инструкции разработаны так, что они
не могут повредить или модифицировать значения. Эти ограничения на манипуляции со стеком
операндов вызваны проверками в
В любой момент времени стек операндов имеет определенною глубину, причем значения с типами
и
занимают две единицы памяти стека, остальные типы занимают по одной единице
памяти стека.
Стек операндов
Чтобы реализовать
динамическое связывание
кода метода, каждый фрейм (см.
) содержит ссылку
на тип текущего метода в хранилище констант времени выполнения (см.
). Код в
метода ссылается на те методы, которые необходимо будет вызвать и те переменные, доступ к
которым нужно получить по символьным ссылкам. Динамическое связывание преобразует ссылки на
методы в виде символов исходного кода на Java в реальные ссылки, при необходимости загружая
классы для тех ссылок, которые еще не определены; переменные исходного кода преобразуются в
соответствующие ссылки в структурах данных, связанных с этими переменными.
Позднее связывание методов и переменных позволяет писать код более устойчивый к изменениям.
Вызов метода
завершается нормально
, если вызов не приводит к возникновению исключения (см.
), причём, неважно будет ли исключение вызвано непосредственно из виртуальной машины Java
либо явным вызовом оператора
. Если вызов метода завершается нормально, в вызывающий
метод может быть передано значение. Это происходит, когда вызываемый метод выполняет одну из
инструкций возврата (см.
); какую именно – зависит от типа возвращаемого значения (если
вообще таковое имеется).
Текущий фрейм (см.
) в этом случае используется для восстановления вызывающего метода,
включая состояние локальных переменных и стека операндов; программный счётчик вызывающего
метода соответственно увеличивается, чтобы избежать повторного вызова метода, который только что
был вызван. Управление успешно передается в код фрейма вызывающего метода; результат
выполнения (если таковой имеется) записывается в стек операндов вызывающего метода.
Вызов метода
завершается аварийно
, если выполнение инструкций виртуальной машины Java
находящихся в теле метода приводит к тому, что виртуальная машина формирует исключение и это
исключение не перехвачено в методе. Выполнение любой инструкции
(см.
) также
приводит к явному формированию исключения, и если исключение не перехвачено текущим методом,
то метод завершается аварийно. Метод, завершившийся аварийно никогда не возвращает значения в
вызывающий метод.
Динамическое связывание
Нормальное завершение вызова метода
Аварийное завершение вызова метода
Виртуальная машина Java не обязывает разработчика к какой-либо определённой внутренней
структуре объектов.
Примечание. В некоторых реализациях виртуальной машины Java выполненных компанией Oracle, ссылка
на класс представляет собой ссылку на
, который сам по себе состоит из пары ссылок: одна
указывает на таблицу методов объекта, содержащую также ссылку на объект
представляющий
тип объекта, а другая на область данных в куче, содержащую члены-данные объекта.
Виртуальная машина Java реализует множество правил арифметики чисел с плавающей точкой,
которое является подмножеством правил стандарта
Операции с плавающей точкой виртуальной машины Java не формируют исключений, захватов или
других сигналов определённых стандартом IEEE 754 сообщающих об исключительной ситуации:
неверная операция, деление на ноль, переполнение, исчезновение значащих разрядов, потеря
точности. Виртуальная машина Java не сигнализирует специальным образом о том, что в ходе
вычислений получено NaN значение.
Виртуальная машина Java не использует механизм сигнализирования при сравнении чисел с
плавающей точкой.
Операции округления в виртуальной машине Java всегда используют режим округления к
Представление объектов
Арифметика чисел с плавающей точкой
Арифметика чисел с плавающей точкой виртуальной машины Java и
стандарт IEEE 754
ближайшему числу стандарта IEEE 754. Неточные результаты вычислений округляются к
ближайшему представимому значению, путём сложения старшего бита остатка с младшим
значащим битом неокругленного целого. Это стандартный режим округления IEEE 754. Но
инструкции виртуальной машины Java преобразующие типы с плавающей точкой к целочисленным
типам всегда округляют в направлении нуля. Виртуальная машина Java не предоставляет никаких
средств управления режимом округления чисел с плавающей точкой.
Виртуальная машина Java не поддерживает ни расширенного формата одинарной точности, ни
расширенного формата двойной точности определяемых стандартом IEEE 754 за исключением
того, что насколько это допустимо наборы значений двойной точности и двойной точности с
расширенной экспонентой могут рассматриваться как реализация расширенного формата
одинарной точности. Элементы множества значений чисел с плавающей точкой с расширенной
экспонентой с одинарной и двойной точностью
не соответствуют
значениям, представленным в
стандарте IEEE 754 расширенным форматом одинарной точности и расширенным форматом
двойной точности соответственно: стандарт IEEE 754 требует не только увеличения пределов
значения экспоненты, но увеличения точности (увеличения числа значащих битов мантиссы –
прим. перев.
Для каждого метода каждого класса определён
режим работы с плавающей точкой
, который может
быть
FP-strict
или
не FP-strict
. Режим работы с плавающей точкой задается установкой флага
набора
Режимы работы с плавающей точкой
strict могут превосходить значения с режимом FP-strict, кроме случаев запрещённых правилами
преобразования множества значений.
Для реализации виртуальной машины Java, которая поддерживает числа с расширенной экспонентой,
допустимо или необходимо, в зависимости от обстоятельств, преобразовывать значения чисел с
расширенной экспонентой к значениям чисел со стандартной экспонентой. Такое
преобразование
множества значений
не является преобразованием типов, но лишь преобразование значений в
пределах одного и того же типа.
Когда необходимо выполнить преобразование множеств значений, допустимо выполнять следующие
операции над значениями:
Если значение типа
и не принадлежит стандартному набору значений, оно округляется до
ближайшего из элементов стандартного набора.
Если значение типа
и не принадлежит стандартному набору значений, оно округляется до
ближайшего из элементов стандартного набора.
В дополнение к выше сказанному при необходимости выполнить преобразование множеств значений,
выполняются следующие операции:
Предположим выполнение инструкции виртуальной машины Java, являющейся не FP-strict, привело
к тому, что значения типа
помещено в стек операндов, который является FP-strict либо как
параметр метода, либо как локальная переменная, поле класса или элементе массива. Если
значение не принадлежит стандартному набору значений, оно округляется до ближайшего из
элементов стандартного набора.
Предположим выполнение инструкции виртуальной машины Java, являющейся не FP-strict, привело
к тому, что значения типа
помещено в стек операндов, который является FP-strict либо как
параметр метода, либо как локальная переменная, поле класса или элементе массива. Если
значение не принадлежит стандартному набору значений, оно округляется до ближайшего из
элементов стандартного набора.
Не все значения с расширенной экспонентой могут быть точно преобразованы стандартные значения.
Если значение, которое нужно преобразовать, слишком велико, чтобы быть представленным точно
(его экспонента больше чем может храниться в стандартном наборе), то оно преобразовывается в
бесконечность (положительную или отрицательную) соответствующего типа. Если значение, которое
нужно преобразовать, слишком мало, чтобы быть представленным точно (его экспонента меньше чем
может храниться в стандартном наборе), то оно округляется к ближайшему допустимому
денормализованному значению или нулю того же знака.
Правила преобразования множества значений
Правила преобразования множества значений сохраняют бесконечности и не-числа (NaN) и не могут
изменить знак преобразуемого значения. Правила преобразования множества значений относятся к
значениям, только принадлежащим типам с плавающей точкой.
На уровне виртуальной машины Java, каждый конструктор, написанный на языке программирования
Java, представляет собой
инициализирующий метод экземпляра
, у которого есть специальное имя
. Это имя формирует компилятор. Поскольку имя
не является действительным
идентификатором, его невозможно непосредственно использовать в языке программирования Java.
Инициализирующий метод экземпляра может быть вызван только виртуальной машиной Java с
помощью инструкции
invokespecial
, и этот метод может быть вызван только для уже
инициализированного экземпляра класса. Инициализирующий метод экземпляра имеет те же права
доступа, что и конструктор, от которого он был произведён.
Класс или интерфейс имеет как минимум один
инициализирующий метод класса или экземпляра
соответственно; инициализация класса или интерфейса происходит путём вызова инициализирующего
метода. Такой метод имеет специальное имя
, не имеет аргументов и является
).
Примечание. Если в
файле есть несколько методов с именем
, то действителен только
один – другие не имеют смысла. Их нельзя вызвать ни одной из инструкций виртуальной машины Java и
сама виртуальная машина Java их никогда не вызывает.
файле с версией 51.0 или выше метода должен иметь флаг
с установленным
значением, для того чтобы метод был инициализирующим методом класса или интерфейса.
Примечание. Это требование введено в Java SE 7. В
файле, чья версия 50.0 или ниже метод с
именем
, имеющий тип
и не имеющий аргументов, рассматривается как
инициализирующий метод класса или интерфейса вне зависимости от установленного флага
Имя
формирует компилятор. Поскольку имя
не является действительным
идентификатором, его невозможно непосредственно использовать в языке программирования Java.
Инициализирующий метод класса или интерфейса неявным образом вызывается виртуальной машиной
Java; его невозможно вызвать непосредственно по инструкции виртуальной машины Java, но он
неявно вызывается в процессе инициализации класса.
Специальные методы
Метод считается
сигнатурно полиморфным
, тогда и только тогда, когда выполнены следующие
Метод объявлен в классе
Метод имеет только один формальный параметр типа
Метод возвращает значения типа
Для метода установлены флаги
и
Примечание. В Java SE 7 сигнатурно полиморфными методами являются методы
и
класса
Выполнена инструкция
В ходе выполнения инструкций виртуальная машина Java обнаружила условия, приводящие к
аварийной ситуации. Этот тип исключений не возникает в произвольной точке кода, а
формируется сразу после обнаружения аварийной ситуации и имеет строгую привязку к
инструкциям, которые либо:
Определяют исключение в качестве возможного результата выполнения:
Когда инструкция представляет собой операцию, нарушающую семантику языка
программирования, например выход индекса за границы массива.
Когда возникает ошибка при загрузке или связывании частей программы.
Приводят к нарушению ограничений, накладываемых на ресурсы, например, использование
слишком большого количества памяти.
Возникло асинхронное исключение по следующим причинам:
Вызван метод
класса
или
Возникла внутренняя ошибка, связанная с реализацией виртуальной машины Java.
Метод
может быть вызван одним потоком, чтобы остановить другой поток или все потоки в
определённой группе потоков. Эти исключения асинхронны, поскольку могут произойти в любой
момент выполнения потока или потоков. Внутренняя ошибка Java машины также считается
асинхронной (см.
Виртуальная машина Java позволяет выполнять небольшое число дополнительных операций, перед
тем как асинхронное исключение будет выброшено. Задержка, вызванная выполнением
дополнительных операций, оправдана тем, что оптимизированный код может обнаружить и выбросить
эти исключения в точках, где целесообразно обработать их в соответствии с правилами семантики
языка программирования Java.
Примечание. Простая реализация виртуальной машины последовательно проверяет условия для всех
асинхронных исключений для каждой инструкции передачи управления. Поскольку программа имеет
конечный размер, это определяет верхнюю границу интервала времени общей задержки, вызванной
проверкой возникновения асинхронных исключений. Поскольку асинхронные исключения не возникают
между инструкциями передачи управления, для генераторов кода допустима гибкость в изменении
порядка операций между инструкциями передачи управления для улучшения производительности кода.
Для более подробного ознакомления с вопросом рекомендуем статью Марка Фили
Polling Efficiently on
Stock Hardware by Marc Feeley, Proc. 1993 Conference on Functional Programming and Computer Architecture
Copenhagen, Denmark, pp. 179–187.
Исключения, выбрасываемые виртуальной машиной Java, сохраняют целостность данных в следующем
смысле: когда происходит передача управления вследствие формирования исключения, то результаты
выполнения всех инструкций вплоть до точки возникновения исключения доступны виртуальной
машине. Инструкции, находящиеся после точки возникновения исключения не выполняются и не
влияют на результат вычислений. Если оптимизатор кода неявно вычислил инструкции, которые
следуют после точки возникновения исключения, он должен позаботиться о том, чтобы отменить их
влияние на текущее состояние программы.
С каждым методом в виртуальной машине Java может быть связано от нуля и более
. Обработчик исключения определяет величину смещения в машинном коде, указывая на
тот метод, которому принадлежит исключения, описывает тип исключений, которые обработчик может
обработать, и определяет положение первой инструкции кода метода для обработки исключений.
Исключение соответствует обработчику исключений, если смещение инструкции, которая вызвала
исключение, находится в пределах смещений обработчика исключений и тип исключения является
классом или наследником класса исключения, которое может обрабатывать обработчик. Когда
выбрасывается исключение, то виртуальная машина Java ищет соответствующий обработчик
исключения в текущем методе. Если подходящий обработчик найден, то система переходит к
выполнению кода, указанного в обработчике исключений.
Если в текущем методе не найдено подходящего обработчика исключений, выполнение текущего
метода завершается аварийно (см.
). При аварийном завершении работы метода стек операндов,
и локальные переменные теряются, текущий фрейм удаляется из стека фреймов, затем текущим
фреймом становится фрейм вызывающего метода. После этого исключение выбрасывается повторно,
но уже в контексте фрейма вызывающего метода и так далее, по цепи вызовов методов. Если
подходящего обработчика исключений так и не было найдено, прежде чем достигнута вершина цепи
вызовов методов, то поток, в котором было выброшено исключение прекращает свою работу.
Порядок, в котором производится поиск обработчиков исключений, имеет значение. Внутри
файла обработчики исключений для каждого метода хранятся в таблице (см.
). Во время работы
программы, когда выброшено исключение, виртуальная машина Java производит поиск обработчиков
исключений в текущем методе в порядке, в котором они записаны в соответствующей таблице в
файле, начиная с начала таблицы.
Обратите внимание, что виртуальная машина Java не накладывает ограничений на порядок
следования обработчиков исключений в таблице. Соответствие порядка следования обработчиков
исключений семантике языка Java обеспечивается только компилятором (см.
). В случае, если
файл генерируется не компилятором, а другими средствами, процедура поиска гарантирует,
что все реализации виртуальной машины будут одинаково обрабатывать таблицу обработчиков
Набор инструкций виртуальной машины Java состоит из
кода операции
, за которым следует ноль или
более
, аргументы операции или данные, используемые операцией. Большое количество
инструкций вообще не имеют операндов и состоят только из кода операции.
Без учёта исключений внутренний цикл интерпретатора виртуальной машины Java работает
следующим образом:
do {
автоматически вычислить значение регистра pc и извлечь код операции по адресу pc;
if (есть операнды у операции?) извлечь операнды;
выполнить операцию с извлечённым кодом и операндами;
} while (есть еще операции?);
Обзор инструкций
Количество и размер операндов определяется кодом операции. Если размер операнда превышает
один байт, то он хранится в
обратном порядке
: сначала старший байт, затем младший. Например,
беззнаковый 16-ти битный индекс локальной переменной хранится как два беззнаковых байта,
, так что значение адреса вычисляется следующим образом: (
8)|
Коды инструкций (байт-код) выровнены побайтово. Есть два исключения из этого правила: инструкции
lookupswitch
и
tableswitch
, которые дополнительно требуют 4-х байтового выравнивания своих
операндов.
Примечание. Решение ограничить размер кода операции одним байтом и отказаться от выравнивания
данных скомпилированного кода отражает желание сделать код более компактным, возможно за счёт
скорости его работы в командах реального процессора. Размер кода операции в один байт также
ограничивает количество операций. Отказ от выравнивания большего, чем один байт, означает, что
данные размером больше байта конструируются из байтов во время выполнения программы.
Большинство инструкций виртуальной машины Java содержат в своём названии описание типа
данных, с которым они работают. Например, инструкция
iload
загружает содержимое локальной
переменной, которая должна быть типа
в стек операндов. Инструкция
fload
делает то же самое
для значения типа
. Эти две инструкции делают одно и то же (отличие лишь в типах данных),
но имеют разные коды операций.
Для большинства типизированных инструкций, её тип можно узнать по букве в мнемоническом
обозначении кода операции:
для операций над типом
,
для
,
для
,
для
,
для
,
для
,
для
, и
для
. Некоторые инструкции, тип которых
определяется однозначно из самой инструкции, не имеют буквы указателя типа в своём
мнемоническом обозначении. На пример
arraylength
всегда оперирует с массивами и ничем иным.
Некоторые инструкции, такие как
– безусловная передача управления – вообще не требуют
типизированных операндов.
Существующее в виртуальной машине Java ограничение на размер кода операции в один байт
существенно влияет на содержимое набора команд. Если каждая типизированная инструкция будет
поддерживать все типы данных времени выполнения виртуальной машины Java, то количество
инструкций превысит то, которое можно хранить в одном байте. Вместо этого набор инструкций
виртуальной машины Java ограничивает число операций для каждого типа. Другими словами набор
инструкций был умышленно сделан не ортогональным по отношению к существующим типам.
Существует класс инструкций для перевода одних типов в другие используемый для выполнения
Типы данных и виртуальная машина Java
операций над неподдерживаемыми типами.
В таблице 2.2 приведены инструкции и типы данных, которые они поддерживают. Конкретную
инструкцию с информацию о типе операнда можно получить, заменяя символ
в шаблоне
мнемонического обозначения операции (см. колонку операционных кодов) на тип из колонки типов.
Например, существует инструкция
iload
загрузки значений типа
, но нет аналогичной инструкции
для типа
Обратите внимание, что для целочисленных типов
,
и
большинство инструкций в
таблице 2.2 отсутствует. А операций с типом
вообще нет. Компилятор осуществляет загрузку
данных с типами
и
, используя инструкции виртуальной машины Java, которые расширяют
с учетом знака значения указанных типов к типу
во время компиляции или выполнения
программы. Загрузка литералов с типами
и
выполняется с использованием инструкций
виртуальной машины Java, которые расширяют значения указанных типов (предварительно обнулив
знак в значении типа
, т.н. нулевое расширение –
прим. перев.
) к типу
во время компиляции
или выполнения программы. Точно также загрузка значений из массивов с типами
,
, и
осуществляется посредством знакового или нулевого расширения указанных типов к
и последующей работе этим типом. Поэтому большинство операций над значениями с типами
,
,
и
выполняется через преобразование к типу
и дальнейшее
выполнение соответствующей операции.
Таблица 2.2 Поддержка типов в операциях виртуальной машине Java
Код операции
Tipush
sipush
iconst
lconst
Tload
iload
lload
fload
aload
istore
lstore
Tinc
iinc
Taload
baload
saload
iaload
laload
faload
caload
aaload
iastore
lastore
iadd
ladd
isub
lsub
imul
lmul
Tdiv
idiv
ldiv
fdiv
ddiv
irem
lrem
ineg
lneg
ishl
lshl
ishr
lshr
iushr
lushr
iand
land
ior
lor
ixor
lxor
i2T
i2b
i2s
i2l
i2f
i2d
l2T
l2i
l2f
l2d
lcmp
if_TcmpOP
if_icmpOP
if_acmpOP
Загрузить локальную переменную в стек операндов:
iload
,
il&#xn000;oad_n
,
lload
,
ll&#xn000;oad_n
,
fload
fload_n00;n
,
,
,
aload, al&#xn000;oad_n
Считать значение из стека операндов в локальную переменную:
istore
,
istore_n00;n
,
lstore
lstore_n00;n
,
,
fstore_n00;n
,
,
dstore_n00;n
,
,
Загрузить константу в стек операндов:
,
sipush
,
ldc
,
ldc_w
,
ldc2_w
,
aconst_null
,
iconst_m1
iconst_ii10;
,
lconst_ll10;
,
,
dconst_ᴀd
Получения доступа к новым локальным переменным через расширение количества байт в индексе:
wide
Инструкции доступа к полям объекта и элементам массива (см.
) также перемещают данные в
Инструкции загрузки и считывания
и из стека операндов.
Для обозначения семейства инструкций, в мнемонической записи, приведенных выше команд, между
угловыми скобками добавлены спецсимволы; например
il&#xn000;oad_n
означает набор
iload_0
,
iload_1
iload_2
и
iload_3
. Такие семейства инструкций (без аргументов) являются частными случаями
инструкции
iload
, у которой только один операнд. Для инструкций, являющихся частным случаем,
операнд задан не явно и нет необходимости хранить его где-либо. В остальном смысл инструкций
полностью совпадает (например,
iload_0
означает то же что и
iload
с нулевым операндом). Буква
между угловыми скобками определяет тип неявного операнда для семейства инструкций:
n00;n
неотрицательное целое,
ii10;
для
,
ll10;
для
,
ἀf
, a
и
ᴀd
для
. Инструкции
для типа
часто используются для работы со значениями типа
,
и
(см.
Указанная выше нотация для семейств инструкций используется повсеместно в данной спецификации.
Арифметическая инструкция, вычисляющая некоторый результат, обычно считывает два значения
расположенных на вершине стека операндов, и помещает результат снова в стек. Существуют две
основные разновидности арифметических инструкций: оперирующие целыми значениями и
оперирующие значениями с плавающей точкой. Нет арифметических операций, работающих
непосредственно со значениями типа
,
и
(см.
) или типа
; для
работы с данными типами используются инструкции, работающие с типом
Целочисленные инструкции и инструкции для работы с числами с плавающей точкой так же
отличаются своим поведением при переполнении и делении на ноль. Существуют следующие
арифметические инструкции:
Сложение:
iadd
,
ladd
,
,
Вычитание:
isub
,
lsub
,
,
Умножение:
imul
,
lmul
,
,
Деление:
idiv
,
ldiv
,
fdiv
,
ddiv
Остаток от деления:
irem
,
lrem
,
,
Отрицание:
ineg
,
lneg
,
,
Сдвиг:
ishl
,
ishr
,
iushr
,
lshl
,
lshr
,
lushr
Битовое ИЛИ:
ior
,
lor
Битовое И:
iand
,
land
Битовое исключающее ИЛИ:
ixor
,
lxor
Увеличение локальной переменной на единицу:
iinc
Сравнение:
,
,
,
,
lcmp
Арифметические инструкции
Семантика операторов языка программирования Java над целыми числами и числами с плавающей
точкой полностью поддерживается набором инструкций виртуальной машины Java.
Виртуальная машина Java не сообщает о переполнении во время операции над целыми типами
данных. Единственные целочисленные операции, которые могут вызвать исключение – это
целочисленное деление (
idiv
и
ldiv
) и целочисленное вычисление остатка (
irem
и
lrem
); они
выбрасывают исключение
Инструкции преобразования типов
пользовательском коде и в качестве компенсации отсутствия ортогональности набора инструкций,
имеющихся в виртуальной машине Java.
Виртуальная машина Java непосредственно поддерживает следующий набор расширяющих числовых
в
,
или
в
или
в
Инструкции для расширяющих числовых преобразований следующие:
i2l
,
i2f
,
i2d
,
l2f
,
l2d
и
Мнемоническое описание кодов операций состоит из аббревиатуры типа и символа 2, который
означает предлог «к» (английский предлог to («к») и числительные two («два») имеют схожее
произношение –
прим. перев.
). Расширяющие числовые преобразования не приводят к потере
точности на всем диапазоне числовых величин. На самом деле, расширение от
к
и от
вообще не приводит к потере информации; числовые значения полностью сохраняются.
Преобразования расширения от
к
для режима работы с плавающей точкой FP-strict
(см.
) также полностью сохраняют числовые величины; однако преобразования для режима
работы с плавающей точкой не FP-strict могут приводить к потере информации для любых числовых
Преобразования от
или
значений к
, либо от
к
могут приводить к
потере точности, а именно: потеря младших значащих битов величины; результирующее число с
плавающей точкой представляет собой результат округления при использовании режима округления к
ближайшему, определённого в IEEE 754.
Расширяющее числовое преобразования от
к
представляют собой просто знаковое
расширение представленного в дополнительном коде значения
к более широкому формату.
Расширяющее числовое преобразования от
к целым типам представляет собой беззнаковое
расширение (нуль-расширение) типа
к более широким форматам.
Несмотря на возможную потерю точности, расширяющие числовые преобразования никогда не
приводят к исключениям виртуальной машины Java (не путать с исключениями стандарта IEEE 754
для чисел с плавающей точкой)
Обратите внимание, что не существует расширяющих числовых преобразований от целых типов
и
к типу
. Как указано ранее (см.
) значения типов
,
и
и так
хранятся с использованием типа
, так что эти преобразования выполняются неявно и так.
Виртуальная машина Java непосредственно поддерживает следующий набор сужающих числовых
в
,
или
в
в
или
в
,
или
Инструкции для сужающих числовых преобразований следующие:
i2b
,
i2c
,
i2s
,
l2i
,
,
,
,
. Сужающие числовые преобразования могут вернуть в качестве результата значение с другим
знаком, другим порядком величины или то и другое одновременно; поэтому возможна потеря
Сужающее числовое преобразование
или
в некоторый целый тип
производится путём
отбрасывания всех кроме
старших битов, где
– число битов, используемых в типе
. Это может
привести к тому, что результирующее значение может иметь знак отличный от знака исходного
При сужающем числовом преобразовании чисел с плавающей точкой в некоторый целый тип
, где
или
выполняется следующее:
Если значение с плавающей точкой является не-числом (NaN), то результат преобразования есть 0
с типом
или
В противном случае, значение с плавающей точкой не является бесконечностью, оно округляется к
целому числу
, используя режим округления к нулю стандарта IEEE 754. При этом возможны два
Если
и данное целое значение
представимо в типе
, то результат
преобразования - целое значение
типа
Если
и данное целое значение
представимо в типе
, то результат преобразования -
целое значение
типа
Значение
слишком мало (отрицательное число, очень большое по модулю или отрицательная
бесконечность); в этом случае результатом сужения типа будет наименьшее допустимое
значение типа
или
Значение
слишком велико (положительное число, с большим значением или положительная
бесконечность); в этом случае результатом сужения типа будет наибольшее допустимое
значение типа
или
Сужающее числовое преобразование от
к
выполняется согласно правилам стандарта
IEEE 754. Результатом является корректно округленное значение; режим округления – округление к
ближайшему по стандарту IEEE 754. Если значение слишком мало, чтобы быть представленным в типе
, то результатом преобразования будет положительный или отрицательный ноль типа
если значение слишком велико, чтобы быть представленным в типе
, то результатом
преобразования будет положительная или отрицательная бесконечность. Не-число (NaN) типа
всегда преобразовывается типу не-числу (NaN) типа
Несмотря на переполнение, потерю значащих разрядов, потерю точности сужающие числовые
преобразования никогда не приводят к исключениям виртуальной машины Java (не путать с
исключениями стандарта IEEE 754 для чисел с плавающей точкой).
Не смотря на то, что и классы, и массивы являются объектами, виртуальная машина Java создаёт и
работает с классами и объектами по-разному, используя различные наборы инструкций:
Создание нового экземпляра класса:
Создание нового массива:
,
,
multianewarray
Доступ к полям класса (статические поля (
) известные как переменные класса) и полям
экземпляра (не статические поля, известные как переменные экземпляра):
Загрузить компонент массива в стек операндов:
baload
,
caload
,
saload
,
iaload
,
laload
,
faload
,
daload
aaload
Выгрузить значение из стека операндов в массив:
,
,
,
iastore
,
lastore
,
,
Получить длину массива:
arraylength
Проверить свойства экземпляра класса или массива:
instanceof
,
Существует набор инструкций для непосредственной работы со стеком операндов:
,
,
,
,
,
,
,
Выполнение инструкций передачи управления (условных или безусловных) приводит к тому, что
виртуальная машина Java продолжает выполнение операции отличной от той, которая следует
непосредственно после инструкции передачи управления. Доступны следующие операции:
Условный переход:
ifeq
,
ifne
,
iflt
,
ifle
,
ifgt
,
ifge
,
ifnull
,
ifnonnull
,
if_icmpeq
,
if_icmpne
,
if_icmplt
if_icmple
,
if_icmpgt
,
if_icmpge
,
if_acmpeq
,
if_acmpne
Составные операторы условного перехода:
tableswitch
,
lookupswitch
Операторы безусловного перехода:
,
_w,
,
_w,
Создание и работа с объектами
Инструкции для работы со стеком операндов
Инструкции передачи управления
типами данных
и
. Также виртуальная машина наборы инструкций условного перехода
для проверки нулевых ссылок, поэтому нет требований, чтобы
значение было строго
определённым: его можно выбрать произвольно.
Инструкции условного перехода, использующие для сравнения данные с типами
,
,
выполняются с помощью инструкций, работающих с типом
(см.
). Вместо
инструкций условного перехода сравнивающих значения с типами данных
или
выполняется следующее: используется команда сравнения двух чисел с типами данных
,
или
; результат сравнения в виде значения с типом
помещается в стек операндов. Затем
переход для перехода используется инструкция, работающая с типом
. Из-за активного
использования сравнений с типом данных
виртуальная машина Java имеет расширенной набор
инструкций условного перехода, использующих для сравнения данные с типом
Все инструкций условного перехода, использующих для сравнения данные с типом
знаковое сравнение.
Существуют пять инструкций вызова методов:
invokevirtual
вызывает метод экземпляра с учётом типа объекта (полиморфный вызов – прим.
перев.). Это нормальная диспетчеризация методов в языке программирования Java.
invokeinterface
вызывает метод интерфейса, проводя поиск методов реализованных в экземпляре
во время выполнения программы.
invokespecial
вызывает методы экземпляра, требующие специальной обработки, будь то метод
инициализации экземпляра (см.
), приватный метод или метод родительского класса.
invokestatic
вызывает статические методы класса.
invokedynamic
вызывает метод, который связан с узловым объектом вызова. Перед первым
выполнением метода вызывается инициализирующий метод и в качестве результата узловой
объект вызова связывается виртуальной машиной Java со специфическим лексическим включением
инструкции
invokedynamic
. Поэтому каждое появление инструкции *
invokedynamic
уникальное состояние связывания, в отличие от других инструкций вызова методов.
Существуют следующие инструкции возврата из метода, различаемые по возвращаемому значению:
Вызов методов и инструкции возврата
Программно вызов исключения можно выполнить с помощью инструкции
. Исключения также
могут быть вызваны виртуальной машиной Java при соответствующих условиях.
Виртуальная машина Java поддерживает синхронизацию, как целых методов, так и набора инструкций
с помощью единой конструкции для синхронизации:
Синхронизация на уровне методов выполняется неявно как часть вызова методов и возврата из
методов (см.
). Метод, объявленный как
, отличается от других методов тем, что
в структуре данных времени выполнения
должно превышать число входов в монитор
выполненных потоком
Обратите внимание, что вход и выход в монитор автоматически выполняется виртуальной машиной
Java при вызове синхронизированного метода.
Виртуальная машина Java, реализованная на конкретной платформе, должна предоставить
достаточную поддержку для реализации библиотеки классов. Некоторые классы в этих библиотеках
не могут быть реализованы без тесного взаимодействия с виртуальной машиной Java.
Классы, которые могут потребовать специальной поддержки со стороны виртуальной машины Java,
включают в себя следующие:
Реализацию рефлексии; классы в пакете
и класс
Загрузку и создание классов и интерфейсов. Простейший пример – класс
Компоновку и инициализацию классов или интерфейсов. Приведённые выше примеры также
подходят и для этой категории.
Реализацию политик безопасности; классы в пакете
и другие классы, такие как
Реализацию многопоточности; класс
Реализацию слабых ссылок; классы пакета
Приведённый выше список является скорее иллюстративным и не претендует на полноту.
Исчерпывающий список классов и их функциональности выходит за рамки данной спецификации.
Подробная информация предоставлена в спецификации библиотеки классов платформы Java SE.
На данный момент в этой спецификации мы выполнили лишь общий набросок виртуальной машины
Java: формат
файла и набор инструкций. Эти компоненты жизненно важны для реализации
независимости виртуальной машины Java от аппаратного обеспечения, операционной системы, и
конкретной реализации самой виртуальной машины Java. Разработчику следует рассматривать эти
компоненты скорее как средства обеспечения безопасного взаимодействия реализаций виртуальных
машин Java на разных платформах, чем как заданные раз и навсегда правила.
Важно понимать, где проходит черта между открытым дизайном и закрытой реализацией. Реализация
Библиотека классов
Открытый дизайн, закрытая реализация
виртуальной машины Java должна быть способной читать
файлы и точно реализовывать
семантику кода виртуальной машины Java. Один из способов сделать – это взять данный документ и
реализовать все описанное здесь от точки до точки. Однако для разработчика также допустимо
изменять и оптимизировать реализацию в рамках ограничений данной спецификации, конечно. До тех
пор, пока
файл может быть прочтён и семантика его кода соблюдена, разработчик может
произвольно реализовывать данную семантику. То, что происходит внутри «чёрного ящика» касается
только разработчика, до тех пор, пока тщательно соблюдены все внешние интерфейсы.
Примечание. Из этого правила есть некоторые исключения: отладчики, модули протоколирования
(профайлеры), генераторы кода для динамической компиляции (JIT компиляторы) требуют доступа к
элементам виртуальной машины Java, которые находятся обычно внутри «чёрного ящика». Где это
целесообразно, компания Oracle сотрудничает со сторонними разработчиками виртуальных машин Java и
поставщиками инструментальных библиотек для разработки и распространения описания общих
интерфейсов виртуальной машины Java.
Разработчики могут использовать предоставляемую гибкость, чтобы адаптировать виртуальную
машину Java для повышения производительности, уменьшения использования памяти или
переносимости. Приоритеты в данной конкретной реализации виртуальной машины зависят от целей,
которые ставит перед собой разработчик. Цели могут также включать в себя следующее:
Перевод кода виртуальной машины Java (во время загрузки или во время работы) в набор
инструкций другой виртуальной машины.
Перевод кода виртуальной машины Java (во время загрузки или во время работы) в набор
инструкций центрально процессора, на котором запущена виртуальная машина (иногда
называемый динамической (JIT) компиляцией).
Существование точно специфицированной виртуальной машины и формата файла объектов не
обязательно приводит к ограничению полёта мысли разработчика. Виртуальная машина Java
разработана для поддержки многих реализаций, что даёт новые и интересные решения при полном
сохранении совместимости между реализациями.
Виртуальная машина Java разработана для поддержки языка программирования Java. Библиотека
Oracle JDK содержит компилятор, преобразующий исходный код на языке программирования Java в
набор инструкций виртуальной машины Java и систему, реализующую непосредственно виртуальную
машину Java. Для будущих разработчиков компиляторов полезно знать на примере как компилятор
использует виртуальную машину Java так же, как и полезно понимать собственно устройство
ГЛАВА 3. Компиляция программ в код виртуальной машины Java
виртуальной машины. Некоторые из разделов в этой главе не являются обязательными для
Обратите внимание, что под термином «компилятор» иногда понимается транслятор из набора
инструкций виртуальной машины Java в набор инструкций конкретного центрального процессора.
Один из примеров такого транслятора – это динамический генератор кода (JIT компилятор), который
генерирует платформенно зависимые инструкции только после загрузки кода виртуальной машины
Java. Эта глава не касается генерации платформенно зависимого кода, а посвящена только
компиляции исходного кода на языке программирования Java в набор инструкций виртуальной
машины Java.
Эта глава в основном состоит из примеров исходного кода комментированными листингами кода
виртуальной машины Java, который генерирует компилятор
в составе Oracle JDK версии 1.0.2.
Код виртуальной машины Java написан на так называемом «ассемблере виртуальной машины»,
который был сформирован утилитой
в составе Oracle JDK. Вы можете использовать
чтобы получить собственные дополнительные примеры к приведённым в этой главе.
Формат примеров должен быть знаком для любого разработчика, имевшего дело с ассемблером.
Каждая инструкция имеет формат:
<индекс> <код> [ <операнд1> [ <операнд2>... ]] [<комментарий>]
Здесь
<индекс>
- индекс операции в массиве байт-кодов соответствующего метода. Также индекс
можно рассматривать как байтовое смещение операции относительно начала метода.
<код>
мнемоническое обозначение кода инструкции;
<операндN>
- операнды инструкции (могут
отсутствовать);
[<комментарий>]
- необязательный комментарий в конце строки:
8 bipush 100 // записать в стек константу 100 с типом int
Некоторые комментарии генерирует утилитой
; остальные комментарии написаны авторами.
<индекс>
, предшествующий каждой инструкции может быть использован в инструкциях передачи
управления. Например, инструкция
goto 8
передает управление инструкции с индексом
. Обратите
внимание, что фактическим операндами инструкций передачи управления являются абсолютные
адресные смещения, однако для удобства чтения (мы также используем это в данной главе) утилита
преобразует их в смещения внутри метода.
Операнды, представляющие собой индексы значений в константном пуле, мы снабжаем символом
решетки и комментарием, описывающем значение константы, на которую ссылается индекс,
Формат примеров
10 ldc #1 // Записать в стек константу 100.0 с типом float
9 invokevirtual #4 // Метод Example.addTwo(II)I
В рамках данной главы мы опускаем некоторые детали, такие как размер операндов и другие.
Код виртуальной машины Java содержит некоторые особенности, продиктованные архитектурой
виртуальной машины Java и её типами данных. В первом примере мы обнаружим их в большом
количестве и рассмотрим их более подробно. Метод
выполняет пустой цикл 100 раз:
// Тело цикла пустое
Компилятор преобразует
в следующий байт-код:
// Записать в стек 0 с типом int
// Загрузить в локальную переменную с именем 1 (i=0)
8 // При первом проходе не увеличивать счетчик
1 1 // Увеличить локальную переменную с именем 1 на 1 (i++)
// Записать локальную переменную с именем 1 в стек (i)
100 // Записать в стек константу 100 типа int
11
5 // Сравнить и повторить цикл если результат меньше (i < 100)
14
Использование констант, локальных переменных и управляющих
только с типами значений
. Поэтому все инструкции скомпилированного байт-кода (
iconst_0
istore_1
,
iinc
,
iload_1
,
if_icmplt
) также оперируют только с типами данных
Две константы
и
в методе
записаны в стек операндов с использованием двух различных
инструкций.
записан в стек с помощью инструкции
iconst_0
, одной из семейства инструкций
iconst_ii10;
.
записано с помощью инструкции
, которая записывает следующие за ее байт-
кодом значение непосредственно в стек.
Виртуальная машина Java использует преимущество набора команд, неявно заменяя, где это
возможно, инструкции с часто используемыми операндами (константы типа
,
,
,
,
,
и
случае инструкции
iconst_ii10;
) на их эквивалентные, но более короткие версии. Поскольку для
инструкции
iconst_0
известно, что она записывает в стек значение
0, нет необходимости
дополнительно хранить операнд, так же как и нет необходимости извлекать и декодировать операнд.
Если скомпилировать запись в стек нуля в инструкцию
bipush 0
, то это будет корректно, но приведёт к
увеличению на один байт размера скомпилированного метода
. Это также приведёт к тому, что
виртуальная машина потратит время на извлечение и декодирование явно заданного операнда
каждый раз при проходе цикла. Использование неявных операндов позволяет сделать код более
компактным и эффективным.
Переменная
в методе
хранится в локальной переменной виртуальной машины Java с
именем
. Поскольку большинство инструкций виртуальной машины Java чаще оперируют со
значениями, считанными из стека операндов чем с локальными переменными, то для
скомпилированного кода характерно наличие инструкций записывающих данные из локальных
переменных в стек и обратно. Для таких операций разработаны специальные инструкции в наборе
команд. В методе
, запись и считывание значений локальных переменных происходит с помощью
инструкций
istore_1
и
iload_1
, неявно работающих с локальной переменой
. Инструкция
istore_1
считывает значение типа
из стека операндов и сохраняет в локальной переменной
. Инструкция
iload_1
записывает в стек операндов значение локальной переменной
Способы использования локальных переменных находятся в пределах ответственности разработчика
компилятора. Разработчик должен стремиться использовать инструкции для работы с локальными
переменными везде, где это только возможно. В этом случае результирующий код будет работать
быстрее, иметь меньший размер и использовать фреймы меньшего размера.
Виртуальная машина Java имеет набор инструкций для наиболее частых операций с локальными
переменными. Инструкция
iinc
увеличивает значение локальной переменной на однобайтовое
знаковое значение. В методе
инструкция
iinc
увеличивает первую локальную переменную
(первый операнд инструкции) на
(второй операнд инструкции). Инструкция
iinc
очень удобна для
использования в циклических конструкциях.
Цикл
в методе
реализован в основном следующими инструкциями:
1 1 // Увеличить локальную переменную с именем 1 на 1 (i++)
// Записать локальную переменную с именем 1 в стек (i)
100 // Записать в стек константу 100 типа int
11
5 // Сравнить и повторить цикл если результат меньше (i < 100)
Инструкция
записывает в стек значение
с типом
, затем инструкция
if_icmplt
из стека операндов значение 100 и сравнивает его с переменной i. Если результат сравнения
«истина» (переменная i меньше чем 100), происходит передача управления к индексу 5 и начинается
следующая итерация цикла
. В противном случае управление передаётся инструкции следующей
за
if_icmplt
Если бы в примере с методом spin использовался для счётчика тип, отличный от
, то
скомпилированный код был бы другим и отражал изменения в типе данных счётчика. Например, если
бы вместо типа
был бы тип
как показано ниже:
// Цикл пустой
то в результате компиляции был бы следующий код:
Метод void dspin()
// Записать в стек 0.0 с типом double
// Загрузить в локальные переменные с именами 1 и 2
9 // При первом проходе не увеличивать счётчик
// Записать в стек локальные переменные 1 и 2
// Записать в стек 1.0 с типом double
// Сложить; инструкции dinc нет
// Загрузить в локальные переменные с именами 1 и 2
// Записать в стек локальные переменные 1 и 2
10
#4 // Записать в стек 100.0 с типом double
13
// Инструкции if_dcmplt нет
14
5 // Сравнить и повторить цикл, если результат «меньше» (i < 100.0)
17
никогда не должны рассматриваться отдельно.
Поскольку код операции в виртуальной машине Java занимает один байт, результирующий код
получается очень компактным. Как следствие, это означает, что набор инструкций виртуальной
машины Java очень мал. Поэтому виртуальная машина Java не поддерживает одинаковый набор
операций для всех типов: набор операций не полностью ортогонален (см. таблицу 2.2).
Например, сравнение двух значений типа
в цикле
в примере метода spin может быть
реализовано с помощью одной инструкции
if_icmplt
; тем не менее, не существует одной инструкции в
наборе виртуальной машины Java, которая реализовывала условный переход по результату сравнения
значений типа
. Поэтому в
использовано сравнение значений
) и условный переход по результату сравнения (инструкция
iflt
Операции с типом
наиболее полно представлены в наборе инструкций виртуальной машины Java.
С одной стороны это сделано для более эффективной реализации стека операндов и массивов
локальных переменных. С другой стороны то, что тип
наиболее части используется в программах,
также сыграло свое значение. Поддержка других целочисленных типов реализована в меньшем
объеме. Например, для типов
,
и
нет инструкций хранения, загрузки или сложения.
Вот пример функции
для типа
// Тело цикла пустое
Этот исходный код должен быть скомпилирован для виртуальной машины Java следующим образом: в
качестве основного типа данных выбирается
, там, где это необходимо выполняется
преобразование между
и
, гарантирующее, что значения типа
будут в
соответствующих пределах:
Метод void sspin()
// Значение short рассматривается как int
// Усечение int к short
10
11
13
16
Тип данных
и типы с плавающей точкой имеют среднюю поддержку в наборе инструкций
виртуальной машины Java; единственное, что отсутствует для них – инструкции условной передачи
Виртуальная машина Java обычно выполняет арифметические операции над операндами в стеке.
(Исключение составляет инструкция
iinc
, которая непосредственно увеличивает значение локальной
переменной.) Например, метод
выравнивает значение типа
по степеням двойки:
i,
Доступ к большинству численных констант, объектов, полей и методов можно получить через
константный пул времени выполнения текущего класса. Доступ к объектам будет рассмотрен позже
(см.
). Доступ к типам данных
,
,
и
и ссылкам на экземпляр класса
можно получить с помощью инструкций
ldc
,
ldc
_w и
ldc
Инструкции
ldc
и
ldc
_w используются для доступа к значениям (включая экземпляры класса
) в
константном пуле времени выполнения (кроме значений типа
и
). Инструкция
ldc
используется вместо
ldc
в случае большого числа элементов в константном пуле, что в свою очередь
требует индекса большей размерности для доступа к ним. Инструкция
ldc
2_w предназначена для
доступа к значениям типа
и
; варианта этой инструкции с однобайтовым индексом не
Целочисленные константы типов
,
и
, равно как и некоторые значения типа
могут быть созданы с помощью инструкций
,
sipush
и
iconst_ii10;
(см.
). Некоторые
константы с плавающей точкой могут быть также созданы с помощью инструкций
dconst_ᴀd
Для всех случаев указанных выше компиляция выполняется непосредственно в байт-код без
дополнительных преобразований. Например, для следующих констант:
l1
l2
... выполнить вычисления...
Будет скомпилирован байт-код:
Метод void useManyNumeric()
100 // Записать в стек 100 типа int
// с помощью
(небольшое целое).
#1 // Записать в стек 1000000 типа int;
// для больших целочисленных значений используется
// Для совсем маленьких целых используется
#6 // Записать в стек 0xffffffff типа long (-1 для типа int);
// любая константа типа long может быть
// записана в стек с помощью
11
13
#8 // Записать в стек константу 2.200000 типа double;
// нестандартные double значения
// записываются в стек с помощью
16
Доступ к константному пулу времени выполнения
...выполнить вычисления...
Компиляция операторов
была показана нами выше (см.
). Большинство конструкций передачи
управления языка Java (
,
,
,
и
) компилируется в байт-код
тривиальным образом. Компиляция оператора
описана в отдельном разделе (см.
компиляция исключений – в разделе
, операторов
– в
В качестве дальнейшего примера рассмотрим цикл
; его компиляция достаточно тривиальна;
однако есть некоторые нюансы при компиляции разных типов данных, используемых в операции
сравнения в цикле. Как обычно тип
наиболее поддерживаемый из всех типов, например:
Будет скомпилировано в:
Метод void whileInt()
11
14
Передача управления
доступных для данных типов команд. Это приводит к менее эффективному коду, поскольку
необходимо использовать большее число инструкций виртуальной машины Java, например:
компилируется в:
Метод void whileDouble()
10
#4 // Записать в стек константу 100.1 типа double
13
// Для выполнения сравнения и перехода
// необходимо использовать...
14
5 // ...две инструкции
17
NaN, то инструкция
помещает значение 1 типа
в стек операндов; в этом случае
ifge
выполняет переход. Если
равняется
, то инструкция
помещает значение 0 типа
стек операндов;
ifge
также выполняет переход.
Инструкция
используется при компиляции, если знак в условии цикла поменять на обратный:
), не принадлежат ни одному экземпляру, поэтому
Получение аргументов
необходимости в локальной переменной
Методы, принадлежащие классу, для хранения входных параметров используют локальные
переменные, начиная с
. Если бы метод
был бы статическим (принадлежал классу), то его
параметры передавались бы похожим образом, но с единственным различием: индекс локальных
переменных начинался бы с
, а не с
. Пример:
i,
экземпляр,
. Затем в стек записываются аргументы метода – значения
и
типа
. После
того, как будет создан фрейм для метода
, аргументы, переданные в метод, будут присвоены в
качестве начальных значений локальным переменным. Аргумент типа
и два других
аргумента, записанные в стек вызывающим методом, станут значениями локальных переменных с
именами
,
и
вызываемого метода.
Затем вызывается метод
. Когда метод
завершит выполнение, на вершину стека
операндов вызывающего метода будет записано значение типа
– результат выполнения
Возврат из метода
выполняется посредством инструкции
Инструкция
invokespecial
используется для вызова методов, инициализирующих экземпляр (см.
Она также используется для вызова методов класса-предка (
) и для вызова
Рассмотрим классы
и
, объявленные следующим образом:
Near
Экземпляры классов виртуальной машины Java сознаются с помощью инструкции
машины Java. Напомним, что на уровне виртуальной машины Java конструктор представляет собой
метод с именем
, присвоенным компилятором. Этот специально созданный метод известен как
инициализирующий метод экземпляра (см.
). Для данного класса могут существовать несколько
инициализирующих методов экземпляра, соответственно числу конструкторов. Когда экземпляр класса
был создан и переменным класса присвоены начальные значения (включая экземпляры их
переменные всех предков данного класса), вызывается инициализирующий метод экземпляра.
Работа с экземплярами класса
7
будет скомпилирован в
Метод void createBuffer()
100 // Записать в стек значение 100 (bufsz) с типом int
// Считать из стека bufsz в локальную переменную 2
12 // Записать в стек значение 12 с типом int
// Считать из стека значение в локальную переменную 3
// Записать в стек значение и локальной переменной 2 (bufsz)...
int // ...и создать массив с типами int и длиной bufsz
// Считать из стека значение ссылки и записать в локальную
переменную 1
10
// Записать в стек ссылку на массив из локальной переменной 1
11
10 // Записать в стек значение 10 типа int
13
// Записать в стек значение локальной переменной 3 типа int
14
// Загрузить значения типа int из стека в массив buffer[10]
15
// Загрузить ссылку из локальной переменной 1 в стек
16
11 // Записать в стек значение 11 типа int
18
// Записывать в стек значение из массива buffer[11]...
19
// ... и считать из стека значение в локальную переменную 3
20
Метод int create3DArray()[][][]
10 // Записать в стек 10 (первое измерение)
// Записать в стек 5 (второе измерение)
#1 dim #2// Class [[[I, трёхмерный целочисленный массив
// Необходимо создать только первые два измерения, хранящие ссылки
на другие массивы.
// Величина третьего измерения не задана
// Считать из стека значение ссылки на массив и записать в
локальную переменную 1
// затем загрузить ссылку из локальной переменной 1 в стек
default:34 // Иначе, продолжить с 34
Компилирование операторов
28
// i равно 0; записать в стек константу 0 типа int...
29
при линейном просмотре таблицы. Даже в этом случае инструкция
lookupswitch
выполняет поиск
ключа вместо проверки границ принадлежности выражения и использования значения выражения для
непосредственного вычисления индекса смещения, как это сделано в инструкции
tableswitch
. Поэтому
инструкция
tableswitch
более эффективна, чем
lookupswitch
, в случае если выбор между ними
Виртуальная машина Java имеет большой набор инструкций, работающих со значениями стека
операндов как с не типизированными значениями. Эти инструкции полезны, поскольку виртуальная
машина Java корректно и безопасно манипулирует не типизированными значениями в стеке
операндов. Например,
TestExc
Операции со стеком операндов
Генерация и обработка исключений

(
i
==

0
)

{
будет скомпилировано в:
Метод void cantBeZero(int)
// Записать в стек локальную переменную 1 (i)
12 // Если i==0, создать экземпляр и выбросить исключение
#1 // Создать экземпляр класса TestExc
// Одна ссылка будет передана конструктору
#7 // Метод TestExc.()V
11
// Другая ссылка выброшена в качестве исключения
12
11
// Возврат после обработки исключения TestExc
Таблица исключений:
От До Смещение Тип
0 4 5 Класс TestExc
Вызов метода
– обработчика в операторе
– также компилируется как обычный
вызов метода. Однако наличие оператора
вынуждает компилятор генерировать таблицу
исключений (см.
,
). Таблица исключений для метода
имеет одну строку (для
экземпляра класса
) соответственно одному оператору
в методе
. Если
будет выброшено исключение – экземпляр класса
– во время выполнения инструкций между
индексами
и
в методе
, управление будет передано коду, сгенерированному
виртуальной машиной Java, начиная с индекса
; этот код реализует обработку оператора
Если будет выброшено исключение, не являющееся экземпляром класса
, оператор
метода
не обработает его. Вместо этого исключение будет выброшено повторно для
метода, вызвавшего
Блок
может иметь несколько блоков
TestExc1 e
TestExc2 e
Для компилирования несколько блоков
в блоке
необходимо добавить по одной строке в
таблицу исключений для каждого блока
, а также добавить код вызова обработчика
исключения, который выполнить виртуальная машина Java. Пример:
Метод void catchTwo()
// Начало блока try
#5 // Метод Example.tryItOut()V
(между индексами
и
) будет выброшено исключение
соответствующее одному или нескольким блокам
(исключение является прямым либо не
прямым наследником одного из базовых классов
или
), то будет выбран первый
(самый внутренний) блок
для обработки. Управление передаётся соответствующему
обработчику блока
. Если выброшенное исключение не соответствует ни одному из блоков
метода
, виртуальная машина Java повторно выбрасывает исключение в
вызывающем методе; обработка блоков
метода
больше не производится.
Вложенные блоки
компилируются похожим на рассмотренную выше компиляцию
нескольких блоков
TestExc1 e
TestExc2 e
компилируется в:
Метод void nestedCatch()
// Начало блока try
#8 // Метод Example.tryItOut()V
(индекс операции
) генерирует исключение – экземпляр класса
– то оно будет обработано блоком
, вызывающим
. Это случится, даже
если генерирование исключения произойдёт в пределах внешнего блока
) и даже если этот внешний блок
будет способен обработать исключение.
Один нюанс: обратите внимание, что левая граница предела блока
включает в себя начальное
значение, а правая – не включает конечное (см.
). Поэтому элемент таблицы исключений
соответствующий
не включает в себя инструкцию
Компиляция инструкции
не выбросит исключение, управление будет передано блоку
инструкции
. Инструкция
, расположенная по индексу
выполняет «вызов подпрограммы» -
обработчика блока
с индексом
(такими образом
представляет собой
внутреннюю подпрограмму). Когда выполнение блока
будет завершено, инструкция
16
26 // Переход к блоку finally
19
20
// Начало обработчика исключений
// отличных от TestExc, или исключений
// выброшенных во время работы TestExc
21
26 // Переход к блоку finally
24
// Записать в стек ссылку на исключение...
25
// ...и повторно выбросить исключение в вызывающем методе
26
// Начало блока finally
27
// Записать в стек ссылку this
28
#5 // Метод Example.wrapItUp()V
31
Компиляция инструкций синхронизации
Для кода, написанного на языке программирования Java, наиболее общим способом синхронизации
является использование
методов. Метод является синхронизированным не только,
если он использует инструкции
и
monitorexit
. В константном пуле времени выполнения
для синхронизированного метода установлен флаг
, который проверяется
инструкцией вызова метода (см.
Инструкции
и
monitorexit
используются для компиляции синхронизационных блоков.
Foo f
правил. Эти правила описаны в данном разделе.
Когда компилятор встречает аннотацию, относящуюся к объявлению пакета, он создает
-файл,
содержащий интерфейс, внутреннее имя которого (см.
)
package-name.package-info
Интерфейс имеет уровень доступа по умолчанию («
») и не имеет предков-
интерфейсов. В структуре
установлены флаги
и
. Если
для созданного
-файла номер версии меньше чем 50.0, то флаг
ГЛАВА 4. Формат
-файла
элементов. И хотя мы используем C-подобный синтаксис для обращения к элементам таблицы, это не
значит, что индекс элемента может быть непосредственно преобразован в смещение элемента в
памяти, поскольку каждый элемент может иметь различную размерность.
Там, где мы обращаемся со структурой
-файла как с массивом, это значит, что она состоит из
элементов одинаковой размерности, расположенных непрерывно и по индексу элемента можно
вычислить его смещение в памяти.
Замечание. Мы используем данный шрифт для кода на Prolog, а
этот шрифт
для инструкций
виртуальной машины Java и структур
-файла. Комментарии, добавленные с целью разъяснения
отдельных моментов, представлены по тексту описания структур
-файлов. Комментарии могут
содержать примеры, а также обоснования тех или иных архитектурных решений.
-файл состоит из одной структуры
ClassFile {
u4 зарезервировано;
u2 младшая_часть_номера_версии;
u2 старшая_часть_номера_версии;
u2 количество_константных_пулов;
cp_info константный_пул[количество_константных_пулов-1];
u2 флаги_доступа;
u2 текущий_класс;
u2 предок;
u2 количество_интерфейсов;
u2 интерфейсы[количество_интерфейсов];
u2 количество_полей;
field_info поля[количество_полей];
u2 количество_методов;
method_info методы[количество_методов];
u2 количество_атрибутов;
attribute_info атрибут[количество_атрибутов];
Описание элементов структуры
Представляет собой номер, определяющий формат
-файла. Он имеет значение
младшая_часть_номера_версии, старшая_часть_номера_версии
Значения
и
совместно версию
-файла. Если
-файл имеет старшую часть номера версии равную
M и младшую часть номера версии равную m, то общую версию
-файла мы будем
обозначать как M.m. Поэтому версии формата
-файла могут быть упорядочены
лексикографически, например: 1.5 < 2.0 < 2.1.
Структура
Реализация виртуальной машины Java может поддерживать формат
-файла версии v
тогда и только тогда, когда v находится в пределах Mi.0 ≤ v ≤ Mj.m.
Значения пределов зависят от номера выпуска виртуальной машины Java.
Примечание. Реализация виртуальной машины Java компании Oracle в JDK release 1.0.2 поддерживает
формат
-файла в пределах от 45.0 до 45.3 включительно. Выпуски JDK release 1.0.X поддерживают
формат
-файла в пределах от 45.0 до 45.65535 включительно. Для k ≥ 2, выпуски платформы Java
поддерживают формат
-файла в пределах от 45.0 до 44+k.0 включительно.
Значение элемента
количество константных пулов
равно на единицу больше количества
элементов
в таблице. Индекс в таблице константных пулов считается
действительным, если он больше нуля и меньше значения величины
количество_константных_пулов, с учетом исключения для типов
и
, описанного в
Таблица
это таблица структур (см.
) представляющих различные
строковые константы, имена классов и интерфейсов, имена полей и другие константы, на
которые есть ссылки в структуре
и ее подструктурах. Формат каждой следующей
структуры константный_пул отделяется от предыдущей «маркерным» байтом.
Таблица константных пулов индексируется от 1 до значения
-1.
Значение элемента
представляет собой маску флагов, определяющих уровень
доступа к данному классу или интерфейсу, а также его свойства. Расшифровка установленного
значения флагов приведена в таблице 4.1.
Таблица 4.1 Доступ к классу и модификаторы свойств.
Имя флага
Объявлен как
; доступен из других пакетов.
Объявлен как
; наследование от класса запрещено.
При исполнении инструкции
методы класса предка особым способом.
-файл определяет интерфейс, а не класс.
Объявлен как
; создание экземпляров запрещено.
Класс может быть помечен флагом
компилятором и отсутствует в исходном коде.
Флаг
означает, что класс или его предок, объявлены как перечисления.
Если установлен флаг
, то это означает что задано определение интерфейса.
Если флаг
сброшен, то в
-файле задан класс, а не интерфейс.
Если для данного
-файла установлен флаг
, то флаг
должен быть также установлен (см. JLS §9.1.1.1). При этом такой класс не должен иметь
установленных флагов:
,
и
Если задана аннотация, то флаг
должен быть установлен. Если флаг
установлен, то флаг
должен также быть установлен. Если
флаг
для данного
-файла сброшен, то допустимо устанавливать любые
флаги из таблицы 4.1 за исключением флага
. Однако обратите внимание,
что одновременно флаги
и
не могут быть установлены (см. JLS
Флаг
указывает, какая из двух альтернативных семантик будет подразумеваться в
инструкции
invokespecial
, если она будет использована в данном
-файле. Этот флаг
устанавливается компилятором, преобразующим исходный код в байт-код виртуальной машины
Примечание. Флаг
существует для обратной совместимости с кодом, скомпилированным
более старыми компиляторами языка программирования Java. В выпуске JDK release компании Oracle до
версии 1.0.2 компилятор генерировал набор флагов доступа структуры
, без флага
: на его месте могло быть произвольное значение. Установленное значение этого флага
виртуальная машина Java, реализованная компанией Oracle, игнорировала, впрочем, как и сброшенное.
Все биты элемента
, не обозначенные в таблице 4.1 зарезервированы для будущего
использования. Им должны быть присвоены нулевые значения в
-файле, к тому же виртуальная
машина Java должна их игнорировать.
Значение элемента
должно быть действительным индексом в таблице
. Элемент константный_пул по указанному индексу должен быть
структурой
(см.
), представляющей собой описание класса или
интерфейса, определённого данным
-файлом.
Для класс, значение элемента предок должно быть либо нулем, либо действительным индексом
в таблице константных_пулов[]. Если значение элемента предок не нулевое, то элемент
константный_пул по указанному индексу должен быть структурой CONSTANT_Class_info (см.
), представляющей собой описание непосредственного предка класса, определенного в
данном
-файле. Ни непосредственный предок, ни предок произвольной глубины не
должны иметь установленным флаг
элемента
Если значение элемента предок есть ноль, то
-файл должен описывать класс
единственный класс или интерфейс без непосредственных предков.
Для интерфейсов значение элемента предок должно всегда быть действительным индексов в
таблице
. Элемент
по указанному индексу должен
быть структурой
(см.
), представляющей собой описание класса
Значение
говорит о количестве непосредственных интерфейсов
предков данного класса или интерфейса.
Каждый элемент в массиве
представляет собой действительный индекс в
таблице
. Каждый элемент константный_пул соответствующий индексу,
взятому из таблицы
, где 0 ≤

, должен быть структурой
) представляющей собой интерфейс – прямой предок данного
класса или интерфейса. Индексы интерфейсов в массиве
соответствовать порядку интерфейсов в исходном коде, если читать слева направо.
Элемент
определяет число элементов
в
таблице поля[]
Структура
(см.
) описывает все поля, объявленные в данном классе или
интерфейсе: как принадлежащие экземпляру, так и принадлежащие классу.
Каждое значение в таблице
должно быть структурой
(см.
) дающей
полное описание поля в классе или интерфейсе. Таблица
включает в себя только те
поля, которые объявлены в данном классе или интерфейсе. Она не содержит полей,
унаследованных от класса-предка или интерфейса предка.
Содержит число элементов
Каждый элемент в таблице
должен представлять собой структуру
), дающую полное описание метода в классе или интерфейсе. Если флаги
сброшены в элементе
, то структура
Внутренняя форма имён
Имена классов и интерфейсов, находящиеся в структурах
-файла, представлены всегда в
полной форме в качестве
двоичных имен
(см. JLS §13.1). Имена хранятся в структурах
(см.
) и поэтому там, где это не оговорено особо, для формирования
имен могут быть использованы все символы таблицы Unicode. На имена классов и интерфейсов
допустимо ссылаться из тех структур
(см.
), которые содержать
имена в качестве части своего дескриптора (см.
), а также из всех структур
(см.
По историческим причинам синтаксис двоичных имён в структурах
-файла отличается от
синтаксиса, документированного в JLS §13.1. Символ ASCII ('.'), используемый в качестве разделителя
в двоичном имени в спецификации JLS §13.1, заменён символом ASCII ('/') в двоичном имени в
структурах
-файла. Имена идентификаторов хранятся в сокращённой форме, без имени
указания имён пакетов и классов.
Например, стандартное двоичное имя класса
это
. Во внутренней форме, в
дескрипторах
-файла ссылка на имя класса
реализована как структура
, содержащая строку "
Имена методов, полей и локальных переменных хранятся в
сокращённой форме
(без указания имён
пакетов). Имена в сокращённой форме не должны содержать следующие ASCII символы таблицы
Unicode: '.', ';', '[' или '/'. Имена методов в дополнение ограничены еще и тем, что не могут содержать
(за исключением имен методов
и
, см.
) ASCII символы таблицы Unicode '<'
либо '>'.
Обратите внимание, что имя поля или метода интерфейса может быть
либо
однако вызывать метод
посредством инструкций запрещено. Метод
вызывать только инструкция
invokespecial
– это строка, описывающая тип поля или метода. Дескрипторы в
-файле хранятся в
виде модифицированных строк UTF-8 (см.
) и поэтому могут быть представлены там, где это не
Имена двоичных классов и интерфейсов
Сокращенная форма имен
Дескрипторы и сигнатуры
оговорено особо, символами Unicode.
– это строка, содержащая общую информацию о типе поля или метода, а также
информацию о классе.
Дескрипторы и сигнатуры определяются с помощью грамматики. Грамматика представляет собой
набор порождающих правил, которые описывают, как из последовательности символов может быть
получено синтаксически правильный дескриптор для различных типов. Терминальные символы
грамматики, показаны
шрифтом. Нетерминальные символы показаны
Определение нетерминального символа вводится с помощью имени нетерминального символа и
следующего за ним двоеточия. Одна альтернатив для правых частей определения располагаются
правее нетерминального символа на отдельных строках. Например, в порождающем правиле
Дескриптор поля описывает типы классов, экземпляров или локальных переменных. Он представляет
Грамматика обозначений дескрипторов и сигнатур
Дескрипторы поля
собой последовательность символов, удовлетворяющую правилам грамматики:
Например, дескриптор переменной экземпляра типа
это просто
Дескриптор переменной
экземпляра типа
это
; Обратите внимание, что используется внутренняя
форма двоичного имени класса
Дескриптор переменной экземпляра, являющейся многомерным массивом элементов
][][];это[[[DДескриптор метода
описывает входные параметры метода, а также возвращаемое значение:
Например, дескриптор метода
:
Identifier TypeArguments
:
.
TypeVariableSignature
:


TypeArguments
:

:

*
:
+
-
ArrayTypeSignature
:

TypeSignature
:


Примечание. Сигнатура и дескриптор (см.
) данного метода или конструктора могут не
соответствовать друг другу точно. В частности, число элементов
TypeSignature
, которые описывают
формальные аргументы в
6
Константный пул
12
1
Структура
Дескриптор массива действителен, только если представляет массив, содержащий 255 или менее
Поля классов, методы экземпляров и методы интерфейсов описываются похожими структурами:
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
Структуры
не на интерфейс.
Элемент
в структуре
Структура
Структуры
и
представляют собой 4-х байтные
числовые (
и
) константы:
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
CONSTANT_Float_info {
u1 tag;
u4 bytes;
Элементы этих структур следующие:
Элемент
имеет значение
Элемент
имеет значение
Элемент
представляет собой значение константы с
типом
. Байты значения хранятся в порядке от старшего к младшему.
Элемент
представляет собой значение константы
типа
одинарной точности в формате
754 (см.
). Байты значения хранятся в
порядке от старшего к младшему.
Значение, задаваемое константой
, определяется следующим образом.
Байты значения сначала рассматриваются как набор битов величины с типом
. Затем:
Если
набор битов
равен
, то значение типа
равно положительной
Если
набор битов
равен
, то значение типа
равно отрицательной
Если
набор битов
лежит в пределах от
до
или от
, то значение типа
равно не числу NaN.
Во всех остальных случаях из набора бит вычисляются три величины
,
и
Структуры
и
Тогда значение типа
равно математическому выражению
e-150
Структуры
и
представляют 8-байтовые числовые
и
) константы:
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
Все 8-байтовые константы хранятся в двух элементах таблицы
в
-файле. Если
структура
или
является элементом таблицы
с индексом
, то следующий элемент константного пула расположен по индексу
Индекс
+1 таблицы
является действительным, но не используется для ссылок на
него.
Примечание. Оглядываясь назад, можно сделать вывод, что данное решение было не совсем удачным.
Элементы этих структур следующие:
Элемент
имеет значение
Элемент
имеет значение
,
Беззнаковые элементы
и
представляют значение константы с типом
high_bytes
где байты каждого из элементов
и
хранятся в порядке от старшего к
Структуры
и
Беззнаковые элементы
и
представляют значение константы с типом
двойной точности в формате
754 (см.
). Байты значения хранятся в порядке от старшего к младшему.
Значение, задаваемое константой
, определяется следующим образом.
Байты значения сначала рассматриваются как
набор битов
величины с типом
, который
high_bytes
Если набор битов равен
, то значение типа
равно положительной
Если набор битов равен
, то значение типа
равно отрицательной
Если набор битов лежит в пределах от
до
или от
до
, то значение типа
равно не числу
Во всех остальных случаях из
набора бит
вычисляются три величины
,
и
Тогда значение типа
равно математическому выражению
e-1075
Структура
используется для представления имени поля или метода
без указания того, к какому классу принадлежит поле или метод:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
Элементы структуры
Элемент
имеет значение
Структура
(12).
Элемент
должен быть действительным индексом элемента таблицы
. Содержимое этого элемента должно быть структурой
(см.
), представляющей имя инициализирующего метода
(см.
) либо имя
обычного метода или поля в сокращенной форме (см.
Значение элемента
должно быть действительным индексом в таблице
. Элемент
с указанным индексом представляет собой
структуру
(см.
) – действительный дескриптор поля (см.
или метода (см.
Структура
используется для представления строковых значений:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
Элементы структуры
Элемент
имеет значение
Значение элемента
содержит число байт в массиве
(обратите внимание, что это
не длина результирующей строки). Строки в структуре
не являются
нуль-терминированными.
Массив
содержит собственно байты, из которых состоит строка. Ни один из байтов не
должен принимать значение
и лежать в диапазоне
-
Содержимое строки хранится в модифицированном формате UTF-8. Модифицированный формат UTF-8
позволяет хранить не нулевые ASCII символы, используя при этом только один байт. Все символы
Unicode представимы в модифицированном формате UTF-8.
Структура
Символы в интервале от «
» до «
» хранятся в одном байте:
биты 6-0
7 бит представляют собой значение символа.
Нулевой символ («
») и символы в интервале от «
» до «
» хранятся в двух
и
биты 10-6
биты 5-0
Байты соответствуют символу, чей код равен:
Символы в интервале от «
» до «
» хранятся в трех байтах
,
и
биты 15-12
биты 11-6
биты 5-0
Байты соответствуют символу, чей код равен:
Символы с кодом выше U+FFFF (так называемые дополнительные символы) представлены двумя
псевдо-символами из их представления в UTF-16. Каждый псевдо-символ состоит из трех байт, что
означает, что каждый дополнительный символ занимает шесть байт
,
,
,
,
и
(биты 20-16)-1
биты 15-10
биты 9-6
биты 5-0
Байты соответствуют символу, чей код равен:
0x10000
Байты хранятся в
-файле в порядке от старшего к младшему.
Существует два отличия между данным форматом и «стандартным» форматом UTF-8. Во-первых,
нулевой символ
кодируется с помощью двух байт, а не одного, так что строки в
модифицированном UTF-8 формате не имеют внедрённых нулей. Во-вторых, из стандартного UTF-8
используются только символы, кодируемые одним, двумя или тремя байтами. Виртуальная машина
Java не использует четырёх байтный формат стандартного UTF-8. Вместо него используется
собственный формат из шести байтов.
Примечание. Более подробную информацию по формату UTF-8 смотрите в разделе 3.9 стандарта
Unicode
Encoding Forms of The Unicode Standard, Version 6.0.0
Структура
Структура
), 7 (
) или 9 (
), то имя
метода в структуре
Структура
Структура
Элементы структуры
o следующие:
Элемент
имеет значение
Поле объявлено
0x0010
Поле объявлено
; присваивание после создания объекта
запрещено (см. JLS §17.5).
Поле объявлено
; не кэшируется.
Поле объявлено
; игнорируется при сериализации
объекта менеджером объектов.
таблицы
, определяемые данной спецификацией:
(см.
),
Метод объявлен
; доступен для классов предков.
Метод объявлен
Метод объявлен
; не замещается в классах предках (см.
Метод объявлен
; при вызове метода
захватывается монитор.
Мостовой метод, генерируется компилятором.
Метод с переменным числом параметров.
Метод объявлен
; реализован на языке отличном от
Метод объявлен
; реализация отсутствует.
Метод объявлен
; используется режим работы с
плавающей точкой
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей либо одно из
специальных имен (
) (
или
) либо действительное имя метода в
упрощенной форме (см.
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей действительный
дескриптор метода (см.
Примечание. В последующих редакциях данной спецификации, возможно, будет добавлено требование
того, чтобы последний параметр в дескрипторе метода был массивом, если флаг
элементе
установлен.
Значение элемента
указывает на количество дополнительных атрибутов
(см.
) данного поля.
Каждое значение таблицы
должно быть структурой атрибутов (см.
). Метод
может иметь любое число атрибутов связанных с ним. Допустимы следующие атрибуты
таблицы
не определённым в данной спецификации запрещено влиять на семантику
-файла, но
разрешается предоставлять дополнительную описательную информацию (см.
Атрибуты используются в структурах
(см.
),
(см.
),
Реализация виртуальной машины Java должна считывать и корректно обрабатывать атрибуты
,
и
в
-файле.
Атрибуты
,
Атрибуты
,
Атрибут
должен распознаваться и корректно обрабатываться, если версия
файла равна 49.0 и выше и при этом реализация виртуальной машины Java распознает
файл, чья версия 49.0 и выше.
Атрибут
должен распознаваться и корректно обрабатываться, если версия
файла равна 50.0 и выше и при этом реализация виртуальной машины Java распознает
файл, чья версия 50.0 и выше.
Атрибут
атрибут, для поддержки специального вида отладки, предоставляемого поставщиком. Поскольку
Определение и именование новых атрибутов
реализации виртуальной машины Java должны игнорировать без сообщений об ошибках те атрибуты,
которые они не в состоянии распознать, то
-файл, содержащий дополнительны атрибуты
можно будет выполнить и с помощью других реализаций виртуальной машины, даже, если они не
поддерживают данную отладочную информацию.
Реализациям виртуальной машины Java намеренно запрещено генерировать исключения либо каким-
либо иным способом блокировать использование
-файла только лишь из-за наличия
нераспознанных атрибутов. Дополнительное программное обеспечение, работающее с
-файлом,
может выдавать сообщения об ошибке, если необходимые атрибуты не найдены.
Два различных по смыслу атрибута, использующих одно и то же имя и имеющих одинаковую длину,
приведут к конфликту в той реализации виртуальной машины, которая использует данный атрибут.
Атрибуты, определённые за рамками данной спецификации должны использовать правила
именования, описанные в
Спецификации языка программирования Java SE 7 Edition
(JLS §6.1).
В последующих редакциях данной спецификации могут быть определены дополнительные атрибуты.
Атрибут
является атрибутом с фиксированной длинной в таблице
(см.
). Атрибут
представляет собой значение
константного поля. У заданной структуры
может быть не более одного атрибута
в таблице
. Если поле является статическим (то есть, установлен флаг
, (см. Таблицу 4.19) в элементе
), то константному
полю, описываемому структурой
, присвоено значение, участвующее в процессе
инициализации класса или интерфейса, в котором содержится данное поле (см.
). Чтение
атрибута происходит до вызова метода инициализации класса или интерфейса (см.
Если структура
, представляющая не статическое поле, имеет атрибут
то такой атрибут игнорируется без сообщения об ошибке. Любая реализация виртуальной машины
Java должна уметь распознавать атрибут
Атрибут
имеет следующий формат:
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
Элементы структуры
Атрибут
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
должно быть
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом содержит
константную структуру, которая описывается данным атрибутом. Тип константной структуры
должен соответствовать типу данных поля, как указано в Таблице 4.22.
Таблица 4.22 Типы константных структур и типы полей
Тип поля
Тип константной структуры
,
,
,
,
Атрибут
является атрибутом с переменной длинной в таблице
Атрибут
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
Элементы структуры
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку «
Значение элемента
структуры определяет длину атрибута без учета
начальных шести байт.
Значение элемента
задаёт максимально используемую глубину стека операндов
(см.
) при выполнении инструкций данного метода.
Значение элемента
определяет число локальных переменных, создаваемых при
вызове метода (см.
), включая локальные переменные, используемые для передачи
параметров метода.
Максимальным индексом локальной переменной типа
и
является
- 2.
Максимальным индексом локальной переменной всех остальных типов является
Значение элемента
задает число байт в массиве
для данного метода.
Значение
должно быть больше нуля; массив
не должен быть пустым.
Массив
содержит байты инструкций виртуальной машины Java, которые, собственно, и
реализуют метод.
В случае, когда массив
считывается в память машины с байтовой адресацией и позиция
первого байта массива выровнена по 4-м байтам, то 32-х битные смещения инструкций
tableswitch
и
lookupswitch
также будут выравненны по 4-м байтам. (Подробности, описывающие
принципы выравнивания, приведены в детальном описании каждой из указанных инструкций).
Ограничения для массива
приведены в отдельном разделе (см.
Значение элемента
задает число элементов в таблице
Каждый элемент в массиве
один обработчик исключения метода,
находящегося в массиве
. Порядок обработчиков в массиве
значение (см.
Каждый элемент
содержит следующие поля:
,
Значения двух полей
и
определяют границы кода в массиве
для которого написан обработчик исключений. Значение
должно быть
действительным индексом байт-кода инструкции в массиве
. Значение
должно быть либо действительным индексом байт-кода инструкции в массиве
равно
– длине массива
. Значение
должно быть меньше
Значение
считается включительно, а
не включительно; то есть
обработчик исключений доступен, пока программный счётчик лежит в интервале адресов
start_pc,
Примечание. Тот факт, что конечная точка интервала
рассматривается не включительно,
является исторической ошибкой в проектировании виртуальной машины Java: если размер байт-кода
метода равен 65535 байтам и при этом длина последней инструкции равна одному байту, то эта
инструкция не может быть включена в сегмент кода, для которого срабатывает исключение.
Проектировщик компилятора языка Java может обойти эту ошибку, ограничив 65534 байтами размер кода
метода экземпляра, инициализирующего метода и статического инициализатора.
Значение элемента
определяет начало обработчика исключений. Значение
элемента должно быть действительным индексом в массиве
и указывать на байт-
код инструкции.
Если значение элемента
не нулевое, оно должно быть действительным
индексом в таблице
. Элемент таблицы
с указанным
выше индексом должен быть структурой
(см.
представляющей класс исключения, на которое обработчик призван реагировать.
Обработчик исключения будет вызван, только если выброшенное исключение является
экземпляром или потомком данного исключения.
Если значение элемента
равно нулю, то данный обработчик вызывается
для всех исключений. Он используется для реализации блока
(см.
Значение элемента
указывает на количество атрибутов (см.
) элемента
Каждое значение таблицы
должно быть структурой атрибутов (см.
). Элемент
может иметь любое число атрибутов связанных с ним. Допустимы следующие атрибуты
таблицы
, определяемые данной спецификацией:
(см.
),
Атрибут
когда данное соответствие установлено в
-файле и
термин типизированное состояние
, когда
соответствие используется анализатором типов.
Если атрибут
не имеет атрибута
, то используется
неявный атрибут для
соответствия стекового фрейма
в
-файле, чья версия выше либо равна 50.0. Этот неявный
атрибут эквивалентен атрибуту
, у которого
равно нулю.
Атрибут
имеет следующий формат:
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
Элементы структуры
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
структуры определяет длину атрибута без учета
начальных шести байт.
Значение элемента
определяет количество элементов
в таблице
Массив
содержит структуры
Каждая структура
определяет состояние типов для конкретного смещения байт-
кода. Каждый фрейм содержит явно или неявно значение
Мы говорим, что инструкция в последовательности байт-кодов имеет соответствие стекового фрейма,
если инструкция начинается со смещения
в массиве
атрибута
и при этом атрибут
имеет атрибут
, чей элемент содержит структуру
, относящуюся к
байт-коду со смещением
Структура
состоит из тега размером один байт, за которым следуют ноль или
более байтов, содержащих информацию в зависимости от тега.
Соответствие стекового фрейма может принадлежать нескольким типам:
union stack_map_frame {
Все типы фреймов, даже
используют предыдущие фреймы. Тогда закономерно возникает
вопрос: что использует самый первый фрейм? Самый первый фрейм является неявным и формируется
на основе дескриптора метода. Более подробно смотрите код для
Тип фрейма
представлен тегами в диапазоне [0-63]. Если фрейм имеет тип
, то это значит, что фрейм имеет в точности те же локальные переменные, что и
предыдущий фрейм и число элементов стека равно нулю. Значение
Тип фрейма
представлен тегами в диапазоне [64, 127]. Если
фрейм имеет тип
, то это значит, что фрейм имеет в
точности те же локальные переменные, что и предыдущий фрейм и число элементов стека равно
единице. Значение
Теги в интервале [128-246] зарезервированы для будущего использования.
Тип фрейма
представлен тегом 247. Если фрейм
имеет тип
, то это значит, что фрейм имеет в
точности те же локальные переменные, что и предыдущий фрейм и число элементов стека равно
единице. Значение
Тип фрейма
представлен тегами в диапазоне [248-250]. Если фрейм имеет тип
, то это значит, что стек операндов пуст и фрейм имеет в точности те же локальные
переменные, что и предыдущий фрейм, за исключением того, что последние
переменных отсутствуют. Значение
равно величине 251 –
chop_frame
u1 frame_type
/* 248-250 */
Тип фрейма
представлен тегом 251. Если фрейм имеет тип
, то это значит, что фрейм имеет в точности те же локальные переменные,
что и предыдущий фрейм и число элементов стека равно нулю.
same_frame_extended
u1 frame_type
/* 251 */
Тип фрейма
представлен тегами в диапазоне [252-254]. Если фрейм имеет тип
, то это значит, что стек операндов пуст и фрейм имеет в точности те же
локальные переменные, что и предыдущий фрейм, за исключением того, что дополнительно
определены
локальных переменных. Значение
равно величине
– 251.
append_frame
u1 frame_type
/* 252-254 */
В противном случае
соответствует локальной переменной
+ 2.
Считается ошибкой, если для произвольного индекса
, элемент
содержит локальную
переменную, чей индекс больше чем максимальное число локальных переменных метода.
Тип фрейма
представлен тегом 255.
full_frame
u1 frame_type
/* 255 */
В противном случае
соответствует локальной переменной
Считается ошибкой, если для произвольного индекса
, элемент
содержит локальную
переменную, чей индекс больше чем максимальное число локальных переменных метода.
Нулевой элемент в
представляет собой тип элемента на дне стека, а последующие
элементы в
представляют типы элементов стека операндов по направлению к его вершине.
Мы будем ссылаться на самый нижний элемент стека, как элемент с индексом 0, следующий за ним
– 2 и так далее. Если
соответствует локальной переменной
, тогда
содержит локальную переменную
+1, при условии, что
является одной из структур:
В противном случае
соответствует локальной переменной
+ 2.
Считается ошибкой, если для произвольного индекса
, элемент
содержит элемент стека,
чей индекс больше чем максимальное число элементов в стеке операндов.
Структура
состоит из тега размером в один байт, за которым следует
ноль или более байт с дополнительной информацией о теге. Каждая структура
определяет проверочный тип для одной или двух локальных
union verification_type_info
Тип
соответствует тому, что локальная переменная имеет проверочный тип
Top_variable_info
u1 tag
/* 0 */
Тип
соответствует тому, что локальная переменная имеет проверочный
тип
Integer_variable_info
u1 tag
/* 1 */
Тип
соответствует тому, что локальная переменная имеет проверочный
тип
Float_variable_info
u1 tag
/* 2 */
Тип
соответствует тому, что локальная переменная имеет проверочный тип
Long_variable_info
u1 tag
/* 4 */
Данная структура описывает содержимое двух элементов стека операндов или двух элементов в
массиве локальных переменных.
Если описывается локальная переменная, тогда:
Локальная переменная не должна быть переменной с максимальным индексом.
Локальная переменная со следующим индексом также принадлежит к проверочному
типу
Если описывается элемент стека операндов, тогда:
Элемент стека операндов не должен быть на вершине стека.
Элемент стека операндов, следующий по направлению к вершине стека, также
принадлежит к проверочному типу
Тип
соответствует тому, что локальная переменная имеет проверочный
тип
Double_variable_info
u1 tag
/* 3 */
Данная структура описывает содержимое двух элементов стека операндов или двух элементов в
массиве локальных переменных.
Если описывается локальная переменная, тогда:
Локальная переменная не должна быть переменной с максимальным индексом.
Локальная переменная со следующим индексом также принадлежит к проверочному
типу
Если описывается элемент стека операндов, тогда:
Элемент стека операндов не должен быть на вершине стека.
Элемент стека операндов, следующий по направлению к вершине стека, также
принадлежит к проверочному типу
Тип
соответствует тому, что локальная переменная имеет проверочный тип
Null_variable_info
u1 tag
/* 5 */
Тип
соответствует тому, что локальная переменная имеет
проверочный тип
UninitializedThis_variable_info
u1 tag
/* 6 */
Тип
соответствует тому, что описываемый элемент содержит экземпляр
класса, представленный структурой
(см.
) в таблице
с индексом
Object_variable_info
u1 tag
/* 7 */
u2 cpool_index
Тип
соответствует тому, что описываемый элемент имеет проверочный
тип
Атрибут
Значение элемента
структуры определяет длину атрибута без учёта
начальных шести байт.
Значение элемента
определяет количество
элементов
в таблице
Каждое значение в массиве
должно быть действительным индексом
в таблице
. Элемент таблицы
по указанному выше индексу
должен быть структурой
(см.
), представляющей класс
исключения, который данный метод генерирует.
Примечание. Метод должен генерировать исключение, только если хотя бы одно условие истинно:
Исключение является экземпляром
или его наследника.
Исключение является экземпляром
или его наследника.
Исключение является экземпляром одного из классов, описанных выше в таблице
либо их наследника.
Эти условия налагаются не виртуальной машиной Java, а компилятором во время компиляции.
Атрибут
является атрибутом с переменной длинной, принадлежащим таблице
(см.
). Если константный пул класса или интерфейса C
содержит элемент
, который представляет класс или интерфейс, не
являющийся членом пакета, то структура
класса или интерфейса C должна иметь в
точности один атрибут
в таблице
Атрибут
имеет следующий формат:
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
{ u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
} classes[number_of_classes];
Элементы структуры
Атрибут
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
структуры определяет длину атрибута без учёта
начальных шести байт.
Значение элемента
определяет количество элементов в массиве
Каждый элемент
в таблице
, который соответствует
классу или интерфейсу C, не являющемуся членом пакета, должен соответствовать одному и
только одному элементу массива
Если класс имеет поля, являющиеся ссылками на экземпляр класса или имплементацию
интерфейса, то таблица
(и, следовательно, атрибут
) должны
ссылаться на каждое такое поле, даже если это поле нигде в классе больше не упоминается. Из
данного правила следует, что вложенный класс или интерфейс будут иметь в
информацию для каждого объемлющего класса и для каждого непосредственного члена
Каждый элемент массива
состоит из следующих четырёх полей:
Значение
должно быть действительным индексом в таблице
. Элемент, с указанным выше индексом должен быть структурой
(см.
), представляющей C. Оставшиеся поля элемента
массива
содержат информацию о C.
Если C не является членом-данным класса или интерфейса (т.е. C класс или интерфейс
верхнего уровня (JLS §7.6), или локальный класс (JLS §14.3), или анонимный класс (JLS
§15.9.5)), то значение
должно быть равно нулю.
В противном случае значение
должно быть действительным
индексом в таблице
, а элемент с данным индексом должен быть
структурой
(см.
), представляющей класс или интерфейс,
членом-данным которого является C.
Если C анонимный класс (см. JLS §15.9.5), то значение
должно быть
равно нулю.
В противном случае значение
должно быть действительным
индексом в таблице
а элемент с данным индексом должен быть
структурой
(см.
), представляющей исходное имя C, как оно
записано в исходном коде, на основании которого скомпилирован
-файл.
Значение элемента
представляет собой маску из флагов
для обозначения прав доступа и свойств класса или интерфейса C, как они записаны в
исходном коде, на основании которого скомпилирован
-файл. Флаги показаны в
таблице 4.23.
Таблица 4.23 Флаги доступа и свойств метода
Имя флага
Объявлен
в исходном коде.
Объявлен
в исходном коде
Объявлен
в исходном коде
Объявлен
в исходном коде.
Объявлен
в исходном коде.
в исходном коде
Объявлен
в исходном коде.
Атрибут
Атрибут
Атрибут
Атрибут
Атрибут
),
(см.
) и
содержит информацию о сигнатуре с обобщёнными типами для класса, интерфейса, конструктора или
члена данного, чья обобщённая сигнатура в языке программирования Java содержит ссылку на
переменную типа или параметризированный тип.
Атрибут
имеет следующий формат:
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
Элементы структуры
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
равно двум.
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой сигнатуру класса
(см.
), если атрибут
является атрибутом структуры
; метода, если
атрибут
является атрибутом структуры
u4 attributelength;
Атрибут
u2 sourcefile_index;
Элементы структуры
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
равно двум.
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку.
Строка, на которую ссылается
, интерпретируется как имя файла, из
которого был скомпилирован
-файл. Не предполагается, что данная строка будет
содержать имя директории или абсолютный путь к файлу; такая платформенно зависимая
дополнительная информация должна быть представлена интерпретатором времени выполнения
или средствами разработки в момент, когда имя файла фактически используется.
Атрибут
является необязательным атрибутом постоянной длины в таблице
(см.
). Может быть не более одного атрибута
в таблице
данной структуры
Атрибут
имеет следующий формат:
SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
Элементы структуры
Атрибут
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
содержит длину
атрибута в байтах без учёта начальных шести байт. Поэтому значение элемента
есть число байт в элементе
.debug_extension[]Массив
содержит дополнительную отладочную информацию, которая не
влияет на работу виртуальной машины Java. Информация представлена в виде
модифицированной UTF-8 строки (см.
) без завершающего нулевого байта.
Примечание. Обратите внимание, что массив
может содержать строку, длинна
которой больше, чем длина строки, допустимая в классе
Атрибут
является необязательным атрибутом переменной длины в таблице
атрибута
(см.
). Этот атрибут может быть использован отладчиком для
определения того, какая часть массива
виртуальной машины Java соответствует какой строке в
исходном файле.
Если атрибуты
присутствуют в таблице
данного атрибута
, то
они могут появляться там в произвольном порядке. Более того, несколько атрибутов
совместно могут представлять одну строку в исходном файле; то есть атрибуты
не обязательно взаимно однозначно соответствуют строкам исходного файла.
Атрибут
имеет следующий формат:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
Атрибут
Элементы структуры
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
длину атрибута в байтах без учёта начальных шести байт.
Значение элемента
содержит число элементов в массиве
Каждый элемент массива
сообщает, что номер строки в исходном файле
изменился и принял новое значение в заданном смещении в массиве
. Каждый элемент
должен содержать два следующих элемента:
Значение
указывает на индекс в массиве
, где начинается новый номер
строки в исходном файле соответственно.
Значение
должно быть меньше чем значение элемента
атрибута
, для которого задан атрибут
Значение элемента
должно содержать соответствующий номер строки в
исходном файле.
Атрибут
Атрибут
Атрибут
виртуальной машины Java либо должно быть индексом на один большим размера
массива
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом
должно быть структурой
(см.
), представляющей собой
действительное имя (см.
) локальной переменной.
Значение элемента
должно быть действительным индексом в
таблице
. Значение в таблице
с упомянутым выше
индексом должно быть структурой
(см.
), представляющей
собой дескриптор поля (см.
) определяющий тип локальной переменной в
исходном коде.
Данная локальная переменная должна находиться по индексу
в массиве
локальных переменных текущего фрейма.
Если тип локальной переменной равен
либо
, то её значение расположено
по индексам
и
+ 1.
Атрибут
u2 attribute_name_index;
Атрибут
u4 attribute_length;
u2 local_variable_type_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index;
u2 index;
} local_variable_type_table[local_variable_type_table_length];
Элементы структуры
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом
должно быть структурой
(см.
), представляющей собой
действительное имя (см.
) локальной переменной.
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом
должно быть структурой
(см.
), представляющей собой
сигнатуру типа поля (см.
) определяющую тип локальной переменной в исходном
Данная локальная переменная должна находиться по индексу
в массиве
локальных переменных текущего фрейма.
Если тип локальной переменной равен
либо
, то ее значение расположено
по индексам
и
+ 1.
Атрибут
является необязательным атрибутом постоянной длины в таблице
структур
(см.
),
(см.
) или
Атрибут
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой строку
Значение элемента
равно нулю.
Атрибут
является необязательным атрибутом переменной длины в
таблице
структур
(см.
),
(см.
) или
Атрибут
Значение элемента
содержит число аннотаций времени выполнения,
находящихся в данной структуре.
Примечание. Программный элемент может иметь до 65535 аннотаций времени выполнения языка
программирования Java.
Каждый элемент в таблице
представляет собой одну аннотацию времени
выполнения для программного элемента. Структура аннотации имеет следующий формат:
annotation {
u2 type_index;
u2 num_element_value_pairs;
{ u2 element_name_index;
element_value value;
} element_value_pairs[num_element_value_pairs];
Элементы структуры
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом должно
быть структурой
(см.
), представляющей собой дескриптор поля,
который описывает тип аннотации, представленной в данной структуре
Значение элемента
равно числу пар элемент-значение,
представленных в данной структуре
Примечание. В одной аннотации может содержаться до 65535 пар элемент-значение.
Каждое значение в таблице
представляет собой одну пару элемент-
значение, принадлежащую данной структуре
Каждый элемент из
содержит два следующие элемента:
Значение элемента
должно быть действительным индексом в
таблице
. Значение в таблице
с упомянутым выше
индексом должно быть структурой
(см.
), представляющей
собой действительный дескриптор поля (см.
), который обозначает имя типа
аннотации, находящегося в паре
Элемент
представляет собой значение пары элемент-значение, находящейся в
таблице
Структура
является отдельным элементом, содержащим значение из пары элемент-
значение. Она используется для представления значения элемента во всех атрибутах, которые
описывают аннотации (
,
Структура
Таблица 4.24 Дополнительные значения элемента
Значение элемента
Тип элемента
МассивvalueЭлемент
представляет собой значение аннотации. Он представляет собой объединение
— структура данных, члены которой расположены по одному и тому же адресу. Размер
объединения равен размеру его наибольшего члена. В любой момент времени объединение
хранит значение только одного из членов. – прим. перев.). Элемент
(см. выше) определяет
какой именно элемент из объединения будет использован:
Элемент
используется, если
принимает значение «
», «
», «
», «
», «
», «
» или «
». Значение элемента
должно быть
действительным индексом в таблице
. Значение в таблице
упомянутым выше индексом должно быть структурой, представляющей собой корректную
константу, соответствующую элементу
(см. Таблицу 4.24).
Элемент
используется, если
равен «e». Элемент
состоит из следующих двух элементов:
Значение элемента
должно быть действительным индексом в таблице
. Значение в таблице
с упомянутым выше индексом
должно быть структурой
(см.
), представляющей собой
действительный дескриптор поля (см.
), который обозначает внутреннюю форму
двоичного имени (см.
) типа перечисления, соответствующего структуре
Значение элемента
должно быть действительным индексом в
таблице
. Значение в таблице
с упомянутым выше
индексом должно быть структурой
(см.
), представляющей
собой простое имя константы из перечисления, соответствующей структуре
Элемент
используется, если
равен «c». Значение элемента
должно быть действительным индексом в таблице
Значение в таблице
с упомянутым выше индексом должно быть структурой
(см.
), представляющей собой дескриптор возвращаемого
значения (см.
), определяющий класс для структуры
Примечание. Например, «V» для
;» для
и так далее.
Элемент
используется, если
равен «@». Структура
представляет вложенную аннотацию.
Элемент
используется, если
равен «[». Элемент
состоит из
следующих двух элементов:
Значение
определяет число элементов в массиве, который соответствует
структуре
Примечание. Допустимо не более 65535 значений в массиве.
Каждое значение таблицы
представляет собой значения массива, соответствующего
структуре
Атрибут
похож на
за исключением
того, что аннотации, представленные в атрибуте
не доступны с
помощью механизма рефлексии, до тех пор, пока виртуальной машине Java не будет выдана команда
сделать доступными эти аннотации, например с помощью флага командной строки. Если такой
команды нет, виртуальная машина игнорирует данный атрибут.
Атрибут
Атрибут
является необязательным атрибутом переменной длины в
таблице
структур
(см.
),
(см.
) или
Атрибут
Атрибут
Каждое значение в таблице
Атрибут
Элементы структуры
Атрибут
аннотации других типов. Атрибут
содержит значение по умолчанию для
аннотации, находящейся в структуре
Атрибут
Атрибут
будет завершен аварийно.
Значение элемента
содержит число элементов в массиве
Каждый элемент массива
должен быть действительным
индексом таблицы
. Элемент
по этому индексу должен
быть одной из следующих структур:
(см.
(см.
),
(см.
(см.
),
(см.
(см.
),
Проверка формата
Ограничения для кода виртуальной машины Java
), класса или интерфейса (см.
) хранит в массиве
атрибута
Массив
не должен быть пустым, то есть элемент
не должен быть равным нулю.
Значение
должно быть меньше 65536.
Байт-код первой инструкции должен начинаться с индекса 0.
В массиве
должны присутствовать только коды инструкций, описанных в
Зарезервированные коды инструкций (см.
) или любые не задокументированные данной
спецификацией коды не должны присутствовать в массиве
Если версия
-файла 51.0 или выше, то коды инструкций
и
не должны
присутствовать в массиве
Для каждой инструкции в массиве
за исключением последней, индекс каждой следующей
инструкции равен индексу текущей инструкции плюс длина инструкции, включая все операнды.
Инструкция
wide
рассматривается так же, как и любая другая инструкция; байт-код, который
расширяется с помощью инструкции
wide
, рассматривается как один из операндов этой
инструкции. В ходе работы программы непосредственный переход к этому байт-коду (минуя
wide
не должен совершаться.
Последний байт последней инструкции должен находиться по индексу
- 1.
Статические ограничения для операндов инструкций в массиве
Целевой адрес перехода для каждой из инструкций передачи управления (
,
,
,
ifeq
,
ifne
,
ifle
,
iflt
,
ifge
,
ifgt
,
ifnull
,
ifnonnull
,
if_icmpeq
,
if_icmpne
,
if_icmple
,
if_icmplt
,
if_icmpge
if_icmpgt
,
if_acmpeq
,
if_acmpne
) должен быть байт-кодом операции в границах метода.
Целевой адрес перехода для инструкций передачи управления, не может указывать на операнд
инструкции
wide
(являющийся расширяемой инструкцией). Адрес перехода для такой составной
команды должен всегда указывать на саму инструкцию
wide
Статические ограничения
Каждый целевой адрес, включая адрес по умолчанию каждой инструкции
tableswitch
указывать на байт-код в пределах метода.
Каждая инструкция
tableswitch
должна иметь число элементов в таблице переходов,
соответствующее значениям операндов
и
, при этом операнд
должен быть меньше
либо равен операнду
Целевой адрес перехода для
tableswitch
, не может указывать на операнд инструкции
wide
(являющийся расширяемой инструкцией). Адрес перехода для такой составной команды должен
всегда указывать на саму инструкцию
wide
Каждый целевой адрес, включая адрес по умолчанию каждой инструкции
lookupswitch
указывать на байт-код в пределах метода.
Каждая
lookupswitch
инструкция должна иметь число пар значение-смещение в соответствии со
значением операнда
. Пары значение-смещение должны быть расположены в порядке
возрастания величины знакового значения.
Целевой адрес перехода для
lookupswitch
, не может указывать на операнд инструкции
wide
(являющийся расширяемой инструкцией). Адрес перехода для такой составной команды должен
всегда указывать на саму инструкцию
wide
Операнд каждой из инструкций
ldc
и
ldc_w
должен быть действительным индексом в таблице
. Элемент константного пула по этому индексу должен иметь тип:
,
, либо
, если версия
-файла
меньше 49.0.
,
,
, либо
, если
версия
-файла равна 49.0 или 50.0.
,
,
,
Операнды каждой инструкции
ldc2_w
должны представлять собой действительный индекс в
таблице
. Элемент константного пула по этому индексу должен иметь тип
или
В дополнение к этому, последующий индекс в константном пуле должен также быть
действительным индексом и значение константного пула по этому индексу не должно
Операнды инструкций
Операнды
инструкций
invokevirtual
,
invokespecial
и
invokestatic
должны представлять
собой действительный индекс в таблице
. Элемент константного пула по этому
индексу должен иметь тип
Операнды
инструкции
invokedynamic
должны представлять собой действительный
индекс в таблице
. Элемент константного пула по этому индексу должен иметь тип
Третий и четвёртый байты операндов каждой из инструкции
invokedynamic
должны иметь нулевое
Только для инструкции
invokespecial
допустимо вызывать инициализирующий метод экземпляра
(см.
Никакой другой метод, начинающийся с символа «<» («
») не может быть вызван
инструкцией вызова метода непосредственно. В частности, инициализирующий метод класса или
интерфейса, имеющий имя
никогда не вызывается явно из инструкций виртуальной
машины Java, а вызывается неявно только самой виртуальной машиной Java.
Операнды
инструкции
invokeinterface
должны представлять собой действительный
индекс в таблице
. Элемент константного пула по этому индексу должен иметь тип
Операнды инструкций
instanceof
,
,
и
и операнды
indexbyte
инструкции
multianewarray
должны представлять собой действительный индекс в таблице
. Элемент константного пула по этому индексу должен иметь тип
Инструкция
не должна использоваться для создания массива с более чем 255
Инструкция
не должна ссылаться на элемент
таблицы
представляющий собой массив. Инструкция
не может использоваться для создания массива.
Инструкция
multianewarray
должна использоваться для создания массива, имеющего, по крайней
мере, столько размерностей, сколько указано в его операнде
. Это значит, что от
инструкции
multianewarray
не требуется создавать все размерности (если их в константном пуле,
на элемент которого ссылается операнд
indexbyte
, указано больше чем в операнде
Операнд
каждой инструкции
multianewarray
не должен быть нулём.
Операнд
каждой инструкции
должен принимать одно из значений
(5),
(6),
(7),
(8),
(9),
(10) либо
Операнд индекс каждой из инструкций
iload
,
fload
,
aload
,
istore
,
,
,
iinc
и
Неявный индекс каждой из инструкций
il&#xn000;oad_n
,
fload_n00;n
,
al&#xn000;oad_n
,
istore_n00;n
,
fstore_n00;n
должен быть числом не превосходящим
- 1.
Операнд индекс каждой из инструкций
lload
,
,
lstore
и
должен быть числом не
превосходящим
- 2.
Неявный индекс каждой из инструкций
ll&#xn000;oad_n
,
,
lstore_n00;n
и
dstore_n00;n
быть числом не превосходящим
- 2.
Операнд
каждой инструкции
wide
, модифицирующей инструкции
iload
,
fload
,
aload
istore
,
,
,
Операнд
каждой инструкции
wide
, модифицирующей инструкции
lload
,
,
lstore
либо
должен быть не отрицательным целым числом не превосходящим
- 2.
Структурные ограничения на массив
представляют собой ограничения на взаимное
использование инструкций друг с другом. Существуют следующие структурные ограничения:
Каждая инструкция должна быть выполнена только с соответствующим типом и числом аргументов
в стеке операндов и необходимыми локальными переменными, вне зависимости от того как и
какие инструкции были выполнены до этого.
Для инструкции, работающей со значениями типа
также допустимо оперировать значениями
типа
,
,
и
. (Как указано в
и
виртуальная машина Java
неявно конвертирует значения типов
,
,
, и
к типу
Если инструкция может быть выполнена по нескольким различным путям выполнения, то стек
операндов должен иметь одинаковую глубину (см.
) до момента выполнения инструкции вне
зависимости от выбранного пути исполнения.
Во время выполнения инструкций виртуальной машины запрещено менять местами, разделять или
рассматривать по отдельности части переменной, содержащей значение типа
либо
Если локальной переменной (или паре локальных переменных в случае значений типа
) не присвоено значение, то доступ к ним запрещён.
Во время выполнения инструкций виртуальной машины запрещено увеличивать глубину стека
операндов (см.
) более чем указано элементом
Во время выполнения инструкций виртуальной машины запрещено считывать из стека операндов
больше значений, чем находится.
Каждой инструкции
invokespecial
должен соответствовать инициализирующий метод экземпляра
(см.
). Он может быть в текущем классе или в предке текущего класса.
Если инструкции
invokespecial
соответствует инициализирующий метод экземпляра не из текущего
Структурные ограничения
класса или его предка и целевая ссылка в стеке операндов указывает на экземпляр класса,
созданный ранее инструкцией
, то
invokespecial
должен соответствовать инициализирующий
метод экземпляра из экземпляра класса по целевой ссылке в стеке операндов.
Когда вызывается инициализирующий метод экземпляра (см.
), то неинициализированный
экземпляр класса должен быть в соответствующей позиции в стеке операндов.
Инициализирующий метод экземпляра не должен вызываться для уже инициализированного
экземпляра класса.
Когда вызывается метод экземпляра или производится обращение к переменной экземпляра, то
экземпляр класса, который содержит метод или переменную, должен быть уже инициализирован.
Не инициализированный экземпляр класса не должен находиться в стеке операндов или в
локальной переменной в качестве целевого класса возвратной ветви программы. Исключение
составляет специальный тип не инициализированного экземпляра класса, сам для себя
являющийся целевым для инструкции ветвления (см.
Не инициализированный экземпляр класса не должен находиться в локальной переменной того
сегмента кода, который защищён обработчиком исключений (см.
Не инициализированный экземпляр класса не должен находиться в стеке операндов или в
локальной переменной, если выполняются инструкции
или
Каждый инициализирующий метод экземпляра (см.
), за исключением инициализирующего
метода унаследованного от конструктора класса
, должен вызвать либо другой
инициализирующий метод экземпляра
, либо инициализирующий метод своего
непосредственного предка
, прежде чем можно будет обращаться к членам данным этого
Тем не менее, допустимо присваивать значения членам данным класса
, прежде чем будет
вызван инициализирующий метод экземпляра.
Аргументы метода должны быть должны быть совместимы по типам (см. JLS §5.3) с типами в
дескрипторе метода (см.
Тип каждого экземпляра класса, являющийся целевым для инструкции вызова метода, должен
быть совместим (см. JLS §5.2) с типом класса или интерфейса, определённого в инструкции.
В дополнение, целевой тип инструкции
invokespecial
должен быть совместим с текущим классом за
исключением случая, когда вызывается инициализирующий метод экземпляра.
Тип каждой инструкции возврата из метода должен соответствовать типу возвращаемого значения:
Если метод возвращает
,
,
,
или
, то допустимо использовать
только инструкцию
Если метод возвращает
,
либо
, то соответственно допустимо
использовать только инструкцию
Если метод возвращает ссылочный тип (
), то допустимо использовать только
инструкцию
§5.2) с типом, объявленным в дескрипторе метода (см.
Если
Если
invokevirtual
либо
invokespecial
используются для доступа к
объявленным классе-предке, который находится в пакете, отличном от пакета текущего класса, то
экземпляра класса, к которому производится доступ должен быть тем же что и текущий класс,
либо быть его наследником.
Тип каждого экземпляра класса, к которому обращается инструкция
Тип значения, сохраняемый с помощью инструкций
putfield
или
putstatic
, должен быть совместим с
типом в дескрипторе поля (см.
) класса или экземпляра, где происходит сохранение:
Если тип в дескрипторе есть
,
,
,
либо
, то значение должно
быть
Если тип в дескрипторе есть
,
либо
, то значение должно быть
либо
Если тип в дескрипторе есть ссылочный тип (
), то значение должно быть типом,
совместимым (см. JLS §5.2) с ним.
Тип каждого значения, сохраняемого в массиве с помощью инструкции
, должен быть
ссылочным типом (
Тип компонент массива, сохраняемых помощью инструкции
, также должен быть ссылочным
типом (
Каждая инструкция
должна иметь в качестве аргумента только значения, являющиеся
экземплярами класса
или его наследниками.
Каждый класс, упомянутый в элементе
, в таблице исключений метода должен быть
экземпляром
или его наследником.
Выполнение инструкций никогда не должно заходить за границы массива
Адрес возврата (значение типа
Перейти к инструкциям, которые следуют за инструкциями
либо
можно только с помощью
одной инструкции
Запрещено использовать инструкции
либо
(если управление к ним перешло в результате
возврата из подпрограммы) для рекурсивного вызова подпрограммы, если подпрограмма уже
присутствует в цепи вызова. (Подпрограммы допустимо вызывать из блока
Каждый адрес возврата
Если в результате выполнения инструкции
вызовов выше инструкции
Нет переполнения стека или потери данных из стека
Использование и хранение локальных переменных выполнено корректно.
Аргументы всех инструкций виртуальной машины Java используют правильные типы данных.
Проверка
-файлов
Алгоритм проверки также выполняет проверку без просмотра массива
атрибута
). Операция включает в себя следующее:
Проверка того, что
классы не имеют наследников, а
методы не замещаются (см.
Проверка того, что каждый класс (за исключением
) имеет непосредственного предка.
Проверка того, что удовлетворяет объявленным статическим ограничениям; например, каждая
структура
в константном пуле содержит элемент
действительный индекс в константном пуле для структуры
Проверка того, что все ссылки на поля и методы в константном пуле имеют действительные
имена, действительные классы и действительные дескрипторы типов.
Обратите внимание, что указанные выше проверки не гарантируют, ни того что данное поле или
метод на самом деле существуют в данном классе, ни того что данные дескрипторы типов ссылаются
на действительные классы. Они только гарантируют, что указанные элементы имеют правильную
структуру. Более детальная проверка выполняется, когда проверяется непосредственно байт-код, а
также во время разрешения зависимостей.
Существуют две стратегии, которые может использовать для проверки реализация виртуальной
машины Java:
Проверка сравнением типов должна использоваться для
-файлов, чей номер версии больше
либо равен 50.0.
Проверка логическим выведением типов должна поддерживаться всеми реализациями виртуальной
машины Java, для которых номер версии
-файла меньше 50.0, за исключением тех
реализаций, которые соответствуют профайлам Java ME CLDC и Java Card.
Проверка для реализаций виртуальных машин Java поддерживающих профайлы Java ME CLDC и Java
Card описана в соответствующих спецификациях.
-файл, чей номер версии больше либо равен 50.0, должен быть проверен с использованием
правил проверки типов, данных в этом разделе. Тогда и только тогда, когда номер версии
файла равен 50.0 и проверка типов закончилась аварийно, реализация виртуальной машины Java
может выполнить проверку логическим выведением типов.
Примечание. Эта двойная проверка, спроектирована для упрощения перехода на новый способ проверки.
Многие инструментальные программы, работающие с
-файлами, могут модифицировать байт-код
Проверка сравнением типов
метода таким образом, что может понадобиться выравнивание стековых фреймов метода. Если
инструментальная программа не выполняет необходимого выравнивания стековых фреймов метода, то
проверка типов завершиться аварийно, не смотря на то, что сам байт-код принципиально может быть
верным. Чтобы дать время разработчикам адаптировать их программные инструменты, виртуальная
машина Java будет также поддерживать и старую парадигму проверки, но только ограниченное время.
В тех случаях, когда проверка типов завершается аварийно, но интерфейсный тип успешно используется,
производительность кода уменьшится. Этого уменьшения нельзя избежать. Оно также должно служить
сигналом поставщикам программного обеспечения, что их выходной код нуждается в выравнивании
стековых фреймов, а также дает поставщикам дополнительный мотив для проведения операции
В заключение необходимо отметить, что перехват управления при отказе для проверки интерфейсных
типов поддерживает как постепенное добавление стековых фреймов (если их нет в версии
-файла
50.0, то перехват управления при отказе будет выполнен) и постепенное удаление инструкций
и
из платформы Java SE (если они присутствуют в
-файле версии 50.0, то перехват управления при
отказе будет выполнен).
Если реализация виртуальной машины Java выполняет поверку с помощью интерфейсных типов для
версии
-файла 50.0, то ей необходимо выполнять данную проверку везде, где проверка по
типам закончилась неудачей.
Примечание. Это означает, что реализация виртуальной машины Java не может сделать выбор в одном
случае прибегать к проверке с помощью интерфейсных типов, а в другом случае – нет. Она должна либо
отклонить
-файлы, которые не проверяются с помощью проверки типов, либо последовательно
выполнить перехват управления при отказе для проверки интерфейсных типов, для каждой неудачной
проверки типов.
Для выполнения проверки по типам необходим список стековых фреймов для каждого метода с
атрибутом
. Модуль проверки по типам считывает стековые фреймы для каждого метода и
использует их для подтверждения безопасности типов для каждой инструкции в атрибуте
.
Примечание. Замысел в том, что стековые фреймы должны появляться в начале каждого базового блока
в методе. Стековые фреймы определяют тип проверки для каждого элемента стека операндов и для
каждой локальной переменной в начале каждого базового блока.
Применяющиеся правила проверки типов определены средствами языка Пролог. Текст на русском
языке используется для описания правил проверки типов в свободной манере, в то время как код на
языке Пролог является формальной спецификацией. Тогда и только тогда, когда предикат
не является истинным, система проверки типов может сгенерировать исключение
, означающее, что
-файл сформирован неверно. В противном случае проверка
типов
-файла и проверка байт-кода завершена успешно.
:-
classClassName(Class, Name),
classDefiningLoader(Class, L),
superclassChain(Name, L, Chain),
Chain \= [],
classSuperClassName(Class, SuperclassName),
loadedClass(SuperclassName, L, Superclass),
Примечание. Например, инструкция
aload
представлена термом
, который включает в себя
индекс
, являющийся операндом инструкции.
Операндами некоторых инструкций являются элементы константного пула, представляющие собой
методы, узлы динамического вызова и поля. Метод представлен структурами
кодируются функторами с именами
,
,
,
,
, и
соответственно.
Пример. Инструкция
ldc
для загрузки целого числа 91 будет представлена как
Инструкции в целом представлены как список термов формы
Остальные проверочные типы представлены элементами Пролога, имена которых обозначают
проверочные типы со знаком вопроса.
Примечание. Иными словами, класс
будет представлен как
',
), где
— начальный загрузчик. Типы
и
будут представлены как
) и
',
)) соответственно.
— это список, который может быть как пустым так и иметь один элемент
.
Примечание. Этот флаг используется в конструкторах, чтобы обозначить состояния типов,
инициализация которых ещё не была завершена. В таких состояниях типов запрещено делать возврат из
Использование проверщика типов, приводит к созданию иерархии типов изображённой ниже.
Большинство типов верификатора имеют прямое соответствие с дескрипторами типов виртуальной
машины Java как показано в таблице 4.2. Единственным исключением являются дескрипторы полей
,
,
и
, которые соответствуют типу верификатора
Проверочные типы:
oneWord twoWord
int float reference long double
/ \________________
uninitialized Object
Иерархия типов
Правила подтипов рефлексивны:
isAssignable(X, X).
isAssignable(oneWord, top).
isAssignable(twoWord, top).
isAssignable(int, X)
:- isAssignable(oneWord, X).
isAssignable(float, X)
:- isAssignable(oneWord, X).
isAssignable(long, X)
:- isAssignable(twoWord, X).
isAssignable(double, X)
:- isAssignable(twoWord, X).
isAssignable(reference, X)
:- isAssignable(oneWord, X).
isAssignable(class(_, _), X)
:- isAssignable(reference, X).
isAssignable(arrayOf(_), X)
:- isAssignable(reference, X).
isAssignable(uninitialized, X)
:- isAssignable(reference, X).
isAssignable(uninitializedThis, X)
:- isAssignable(uninitialized, X).
isAssignable(uninitialized(_), X)
:- isAssignable(uninitialized, X).
isAssignable(null, class(_, _)).
isAssignable(null, arrayOf(_)).
isAssignable(null, X)
:- isAssignable(class('java/lang/Object', BL), X),
Эти правила подтипов не обязательно очевидные следствия взаимосвязи типов и подтипов. Есть
чёткое разделение между правилами подтипов для ссылочных типов языка программирования Java и
правилами для оставшихся проверочных типов. Это разделение позволяет нам установить общие
отношения между типами языка программирования Java и остальными проверочными типами.
Отношения справедливы вне зависимости от положения Java типа в иерархии класса, и позволяют
предотвратить излишнюю загрузку классов в реализации виртуальной машины Java. Например, мы не
начнём подниматься вверх по иерархии классов Java если запрос имеет вид
foo,L) :
Правила подтипов для ссылочных типов в языке программирования Java очевидным образом
определены рекурсивно с помощью
. Оставшиеся проверочные типы имеют
правила подтипов следующей формы:
isAssignable(v, X)
:- isAssignable(the_direct_supertype_of_v, X).
Правила подтипов
Где
подтип
, если непосредственный подтип
есть подтип
Также имеется правило, согласно которому отношение подтипов рефлексивно, так что совместно эти
правила покрывают большинство проверочных типов, которые не являются ссылочными типами языка
программирования Java.
isAssignable(class(X, Lx), class(Y, Ly))
:-
isJavaAssignable(class(X, Lx), class(Y, Ly)).
isAssignable(arrayOf(X), class(Y, L))
:-
isJavaAssignable(arrayOf(X), class(Y, L)).
isAssignable(arrayOf(X), arrayOf(Y))
:-
isJavaAssignable(arrayOf(X), arrayOf(Y)).
При присвоениях интерфейсы рассматриваются как
isJavaAssignable(class(_, _), class(To, L))
:-
loadedClass(To, L, ToClass),
isJavaAssignable(From, To)
:-
isJavaSubclassOf(From, To).
Массивы являются подтипами
isJavaAssignable(arrayOf(_), class('java/lang/Object', BL))
:-
Смысл здесь в том, что массив является подтипом
и
isJavaAssignable(arrayOf(_), X)
:-
Отношение подтипов между массивами и примитивными типами это отношение тождества.
isJavaAssignable(arrayOf(X), arrayOf(Y))
:-
X = Y.
Отношение подтипов между массивами и ссылочными типами ковариантно.
isJavaAssignable(arrayOf(X), arrayOf(Y))
:-
compound(X), compound(Y), isJavaAssignable(X, Y).
isArrayInterface(class('java/lang/Cloneable', BL))
:-
isArrayInterface(class('java/io/Serializable', BL))
:-
Наследование рефлексивно.
isJavaSubclassOf(class(SubclassName, L), class(SubclassName, L)).
isJavaSubclassOf(class(SubclassName, LSub), class(SuperclassName, LSuper))
:-
superclassChain(SubclassName, LSub, Chain),
member(class(SuperclassName, L), Chain),
loadedClass(SuperclassName, L, Sup),
loadedClass(SuperclassName, LSuper, Sup).
superclassChain(ClassName, L, [class(SuperclassName, Ls) | Rest])
:-
loadedClass(ClassName, L, Class),
classSuperClassName(Class, SuperclassName),
classDefiningLoader(Class, Ls),
superclassChain(SuperclassName, Ls, Rest).
superclassChain('java/lang/Object', L, [])
:-
loadedClass('java/lang/Object', L, Class),
classDefiningLoader(Class, BL),
Отношение подтипов расширяется к типам состояний.
Массив локальных переменных метода имеет фиксированную длину по построению (в
Заранее оговорённые средства доступа
: повсюду в данной спецификации мы предполагаем
существование определённых предикатов Пролога, чьи формальные определения не даны в
спецификации. Ниже мы приводим данные предикаты и описываем их ожидаемое поведение.
Примечание. Руководящим принципом в определении того какие средства доступа полностью
определены, а какие оговорены и определены заранее, являлось намерение не перегружать
файл
излишним кодом. Предоставляя определённые средства доступа к терму класса или метода, мы
вынуждены полностью определять формат терма Пролога, представляющего
файл.
,
Преобразует дескриптор поля
в соответствующий проверочный тип
начало
для точного определения этого соответствия)
Правила проверки типов
Средства доступа
Истинно тогда и только тогда, когда
не является
,
Возвращает имя
класса предка.
,
Возвращает список
непосредственных предков класса
,
Истинно тогда и только тогда, когда имя пакетов у классов
и
,
Истинно тогда и только тогда, когда имя пакетов у классов
и
Заранее оговорённые средства доступа и утилиты
: мы определяем средства доступа и
вспомогательные правила, которые получают необходимую информацию из описания классов и их
Окружение — это кортеж, состоящий из шести элементов:
объявленный возвращаемый тип метода.
инструкции метода
максимальный размер стека операндов
список обработчиков исключений
maxOperandStackLength(Environment, MaxStack)
:-
Абстрактные методы и методы Native
Проверка кода
Метод с кодом, является безопасным по типам, если возможно объединить код и стек фреймов в один
поток, так что каждое стековое соответствие предшествует инструкции, которой оно соответствует, и
объеденный поток корректен по типам.
имя класса, содержащего метода и L — определяющий загрузчик класса.
mergedCodeIsTypeSafe(Environment, MoreCode, MapFrame).
Допустимо иметь код после безусловного перехода без стекового фрейма для него.
mergedCodeIsTypeSafe(_Environment, [instruction(_, _) | _MoreCode],
:-
write_ln('No stack frame after unconditional branch'),
Объединённый поток кода является безопасным по типам по отношению к входящему состоянию
типов T, если он начинается с инструкции I, которая является безопасной по типу к T и I
удовлетворяет его обработчикам исключений, причём остаток потока безопасен по типу данному
состоянию типов, следующему за исполнением I.
показывает, что будет передаваться следующей инструкции.
показывает, что передаётся обработчикам исключений.
Комбинирование потоков стековых соответствий и инструкций
mergeStackMapAndCode(RestMap, RestCode, RestMerge).
В противном случае, пусть дан список фреймов стековых соответствий, начинающийся с состояния
типа для инструкции по смещению
Обработка исключений
currentClassLoader(Environment, CurrentLoader),
handlerExceptionClass(Handler, ExceptionClass, CurrentLoader),
/* The stack consists of just the exception. */
StackFrame = frame(Locals, _, Flags),
ExcStackFrame = frame(Locals, [ ExceptionClass ], Flags),
operandStackHasLegalLength(Environment, ExcStackFrame),
Изоморфные инструкции
изоморфен другому байт-коду b2, то правило типов для b1 такое же как и для b2.
Манипулирование стеком операндов
мы записываем его, а затем записываем
pushOperandStack(OperandStack, 'void', OperandStack).
pushOperandStack(OperandStack, Type, [Type | OperandStack])
:-
sizeOf(Type, 1).
pushOperandStack(OperandStack, Type, [top, Type | OperandStack])
:-
sizeOf(Type, 2).
Размер стека операндов не должен превышать объявленного максимума.
operandStackHasLegalLength(Environment, OperandStack)
:-
length(OperandStack, Length),
maxOperandStackLength(Environment, MaxStack),
Length = MaxStack.
Тип категории 1 занимает ровно один элемент в стеке. Считывание логического типа категории 1
) возможно, если вершина стека это
и
не равен
(в противном случае он может
означать верхнюю половину типа категории 2). В результате получаем стек, у которого с вершины
снят один элемент.
popCategory1([Type | Rest], Type, Rest)
:-
Type \= top,
sizeOf(Type, 1).
Тип категории 2 занимает два элемента в стеке. Считывание логического типа категории 2 (
возможно, если вершина стека это тип равный
, а элемент непосредственно ниже — это
. В
результате получаем стек, у которого с вершины снято два элемента.
popCategory2([top, Type | Rest], Type, Rest)
:-
sizeOf(Type, 2).
canSafelyPush(Environment, InputOperandStack, Type, OutputOperandStack)
:-
pushOperandStack(InputOperandStack, Type, OutputOperandStack),
operandStackHasLegalLength(Environment, OutputOperandStack).
canSafelyPushList(Environment, InputOperandStack, Types, OutputOperandStack)
:-
canPushList(InputOperandStack, Types, OutputOperandStack),
operandStackHasLegalLength(Environment, OutputOperandStack).
canPushList(InputOperandStack, [Type | Rest], OutputOperandStack)
:-
pushOperandStack(InputOperandStack, Type, InterimOperandStack),
canPushList(InterimOperandStack, Rest, OutputOperandStack).
canPushList(InputOperandStack, [], InputOperandStack).
Инструкции загрузки
Все инструкции загрузки представляют собой разновидности общего шаблона, в зависимости от типа
загружаемого значения.
Загрузка значения с типом
из локальной переменной
является безопасной по типу, если
тип локальной переменной это
, при этом
допустимо присваивать
запись во входящий стек операндов
является допустимым преобразованием типов,
которое приводит к новому состоянию типов
. После выполнения инструкции
загрузки состояние типов будет
loadIsTypeSafe(Environment, Index, Type, StackFrame, NextStackFrame)
:-
StackFrame = frame(Locals, _OperandStack, _Flags),
nth0(Index, Locals, ActualType),
isAssignable(ActualType, Type),
Инструкции сохранения
modifyLocalVariable(Index, Type, Locals, NewLocals)
:-
modifyLocalVariable(0, Index, Type, Locals, NewLocals).
Учитывая
, суффикс списка локальной переменной, начинающийся с индекса I,
изменения локальной переменной
, чтобы она имела тип
приводит к созданию списка
локальных переменных
Если

- 1, то просто копируем вход в выход и идём далее. Если
=
-1, то тип
локальной
может измениться. Это может произойти, если
имеет тип размера 2. Когда мы
установили
в новый тип (и соответствующее значение), то тип/значение
может быть помечен
как недействительный, так как его верхняя половина будет потеряна. Затем мы движемся далее.
Когда мы находим переменную и она занимает только одно слово, мы заменяем её на
Когда мы находим переменную и она занимает два слова, мы заменяем её тип на
, а следующее
слово на
modifyLocalVariable(I, Index, Type,
[Locals1 | LocalsRest], [Locals1 | NextLocalsRest] )
:-
I Index - 1,
I1 is I + 1,
modifyLocalVariable(I1, Index, Type, LocalsRest, NextLocalsRest).
modifyLocalVariable(I, Index, Type,
[Locals1 | LocalsRest], [NextLocals1 | NextLocalsRest] )
:-
I =:= Index - 1,
modifyPreIndexVariable(Locals1, NextLocals1),
modifyLocalVariable(Index, Index, Type, LocalsRest, NextLocalsRest).
modifyLocalVariable(Index, Index, Type,
[_ | LocalsRest], [Type | LocalsRest])
:-
sizeOf(Type, 1).
modifyLocalVariable(Index, Index, Type,
[_, _ | LocalsRest], [Type, top | LocalsRest])
:-
sizeOf(Type, 2).
Мы ссылаемся на локальную переменную, чей индекс непосредственно предшествует локальной
переменной, тип которой будет изменён на
переменная предшествующая индексу
. Будущий тип
переменной предшествующей индексу с типом
это
. Если тип
предшествующей индексу имеет размер 1, то он не меняется. Если тип
предшествующей индексу имеет размер 2, то нам необходимо пометить нижнюю половину его
двухсловного значения как неиспользуемую посредством присваивания ей типа
modifyPreIndexVariable(Type, Type)
:- sizeOf(Type, 1).
modifyPreIndexVariable(Type, top)
:- sizeOf(Type, 2).
Если дан список типов, то мы получаем список, где каждый тип размера 2 замещён двумя
элементами: непосредственно типом и элементом
. В таком случае результат соответствует
представлению списка набора 32-х битных слов в виртуальной машине Java.
expandTypeList([], []).
expandTypeList([Item | List], [Item | Result])
:-
sizeOf(Item, 1),
expandTypeList(List, Result).
expandTypeList([Item | List], [Item, top | Result])
:-
sizeOf(Item, 2),
expandTypeList(List, Result).
В общем случае тип правила для инструкции дан относительно окружения
, которое
определяет класс и метод, в которых встречается инструкция и смещение
Инструкция безопасна по типу.
Можно доказать, что состояние типов после инструкции, выполнившейся успешно, имеет
определённую форму, заданную в
, и состояние типов после инструкции,
выполнившейся аварийно, дано в
Мы старались сделать описание правил на естественном языке легко читаемым, интуитивно понятным
и кратким. Поэтому в описании не повторяются все концептуальные предположения данные ниже. В
Явно мы не упоминаем окружение.
Когда мы говорим о стеке операндов или о локальных переменных, мы ссылаемся на компоненту
состояния типов стека операндов или локальных переменных: либо входящее состояние типов
либо исходящее.
Состояние типов после того как инструкция завершается аварийно, почти всегда равно входящему
состоянию типов. Мы обсуждаем состояние типов после аварийного завершения инструкции только
если оно не равно исходному.
Мы говорим о записи и считывание типов в стек и из стека операндов. Явно мы не обсуждаем
случаи переполнения и антипереполнения стека, но предполагаем, что запись и считывание могут
пройти успешно. Формальные правила для работы со стеком операндов гарантируют, что
необходимые проверки пройдены.
Аналогично, в тексте описана работы только с логическими типами. На практике, некоторые типы
занимают более одного слова. Мы абстрагируемся от этих деталей представления в нашем
Список инструкций
обсуждении, но не в логических правилах которые используют эти типы данных.
Любые неопределённости могут быть разрешены посредством обращения к формальным правилам
Список инструкций JVM с правилами проверки их безопасности
-файл, который не содержит атрибута
(который в этом случае имеет номер
версии 49.0 или ниже) должен быть проверен с использованием типов интерфейсов.
Во время сборки верификатор проверяет массив
аттрибута
для каждого метода
файла с помощью выполнения анализа потока данных для каждого метода. Верификатор гарантирует,
что в любой данной точке программы, не важно каким способом мы достигли этой точки, справедливо
Стек операндов всегда одного и того же размера и содержит одни и те же типы значений.
К локальной переменной не будет предоставлен доступ до тех пор пока не известно, что она
содержит значение соответствующего типа.
Методы вызываются с соответствующими аргументами.
Полям присваиваются значения только соответствующих типов.
Все коды операций имеют аргументы соответствующих типов в стеке операндов и в массиве
локальных переменных.
Не существует неинициализированного экземпляра класса в локальных переменных в коде,
защищённым обработчиком исключений. Однако неинициализированный экземпляр класса может
находится в стеке операндов в коде, защищённым обработчиком исключений. Когда
выбрасывается исключение, то содержимое стека операндов игнорируется.
Из соображений эффективности, некоторые проверки, которые в принципе могли бы быть выполнены
верификатором, откладываются до момента первого вызова кода метода. Поступая таким образом
верификатор избегает преждевременной загрузки
-файлов, откладывая её до тех пор пока она
действительно не будет нужна.
Проверка по типам интерфейса
Процесс проверки по типам интерфейса
Пример. Если метод вызывает другой метод, который возвращает экземпляр класса
и этот экземпляр
присваивается только полю того же типа, то верификатор не выполняет проверку, а действительно ли
класс
существует. Однако, если экземпляр присваивается полю типа
, то определения обоих классов
должны быть загружены для того, чтобы удостовериться, что
является наследником
Код каждого метода проверяется независимо. Во-первых, байты, которые содержаться в методе,
разбиваются на последовательности инструкций и индекс начала каждой инструкции в массиве
помещается в отдельный массив. Затем верификатор проходит по коду второй раз и производит
синтаксический разбор инструкций. Во время этого прохода строится структура данных, которая
содержит информацию о каждой инструкции виртуальной машины Java, находящейся в методе.
Операнды инструкции, если таковые присутствуют, также проверяются на то, что они являются
допустимыми. Например:
Ветви перехода должны быть в границах массива
для данного метода.
Целевые точки всех инструкций передачи управления являются началом других инструкций. В
случае инструкции
wide
сам код инструкции
wide
считается началом инструкции, а код той
инструкции, которую расширяет
wide
. Переходы не на начало инструкции запрещены.
Для инструкций не допустимо получать доступ или изменять локальные переменные, чей индекс
больше или равен количеству локальных переменных, содержащихся в методе.
Ссылка на константный пул должна быть элементом соответствующего типа. (Например,
инструкция
Код не заканчивается на незавершённой инструкции (без нужных аргументов и так далее.)
Процесс выполнения не может перейти за пределы конца кода.
Для каждого обработчика исключения начальная и конечная точка кода, который защищает
обработчик, должна быть началом инструкции либо, в случае конечной точки, быть
непосредственно после кода инструкции. Начальная точка должна быть перед конечной. Код
обработчика исключения должен начинаться с корректной инструкции и не должен начинаться с
кода инструкции, которая расширяется предшествующей инструкцией
wide
Для каждой инструкции метода перед её выполнением верификатор записывает содержимое стека
операндов и содержимое массива локальных переменных. Для стека операндов необходимо знать
размер стека и тип каждого элемента в стеке. Для каждой локальной переменной необходимо знать
либо тип содержимого локальной переменной либо знать что её содержимое не используется или
имеет неопределённое (не инициализированное) значение. Верификатор байт-кода не обязан делать
отличий между объединёнными типами (например
,
,
) во время определения типов в
Верификатор байткода
стеке операндов.
Затем инициализируется анализатор потоков данных. Для первой инструкции метода локальные
переменные, представляющие собой параметры содержать значения для типов, обозначенных в
дескрипторе типов метода; стек операндов пуст. Все остальные локальные переменные содержат
недопустимые (не инициализированные —
прим. перев.
) значения. Для всех остальных инструкций,
которые до сих пор ещё не были рассмотрены, информация относительно стека операндов или
локальных переменных отсутствует.
Затем запускается анализатор потоков данных. Для каждой инструкции бит «изменённого состояния»
сообщает нужно ли рассматривать данную инструкцию. Изначально бит «изменённого состояния»
установлен только для первой инструкции. Анализатор потока данных выполняет следующий цикл:
.
Выбрать инструкцию виртуальной машины Java, чей бит «изменённого состояния» установлен.
Если нет ни одной инструкции чей бит «изменённого состояния» установлен, то проверка
метода успешно завершена. В противном случае сбросить бит «изменённого состояния» у
выбранной инструкции.
.
Провести моделирование запуска инструкции на стеке операндов и массиве локальных
переменных, выполнив для этого следующие:
Если инструкция использует значения из стека операндов, то убедится что на вершине
стека операндов достаточное количество значений и они соответствующего типа. В
противном случае завершить проверку аварийно.
Если инструкция использует локальную переменную, то убедиться что локальная
переменная содержит значение соответствующего типа. В противном случае завершить
проверку аварийно.
Если инструкция записывает значение в стек операндов то, убедиться в стеке операндов
достаточно места для записи новых значений. Добавить обозначенные типы к вершине
моделируемого стека операндов.
Если инструкция изменяет локальную переменную, то записать что теперь локальная
переменная содержит новый тип данных.
.
Определить инструкции, которые должны выполняться после текущей инструкции. Допустимые
могут быть такие инструкции:
Следующая инструкция, если текущая инструкция не является инструкцией безусловного
перехода (например,
,
Целевые точки перехода условного или безусловного ветвления или переключения.
Любой обработчик исключения для данной инструкции.
.
Объединить состояние стека операндов и массива локальных переменных в конце выполнения
текущей инструкции для использования последующими инструкциями. В специальном случае
передачи управления обработчику исключения в стек операндов записывается один объект с
типом исключения, который задан в обработчике исключения. Должно быть достаточно место в
стеке операндов для записи этого одного значения.
Если следующая инструкция в последовательности была рассмотрена впервые, то записать,
что стек операндов и значения локальных переменных, вычисленные в шагах 2 и 3,
являются предшествующими стеком операндов и локальными переменными. Установить бит
«изменённого состояния» у следующей инструкции.
Если следующая инструкция в последовательности уже была рассмотрена ранее, то
объединить состояние стека операндов и массива локальных переменных, вычисленные в
шагах 2 и 3 со значениями, которые там уже есть. Установить бит «изменённого состояния»
у следующей инструкции, если были какие-либо изменения значений.
.
Перейти к шагу 1.
Для объединения двух стеков операндов количество значений в каждом из них должно быть
одинаково. Типы значений в стеках также должны быть одинаковыми, за исключением того для типа
могут присутствовать для разных по типу значения на соответствующих местах в двух
стеках. В этом случае стек операндов после объединения содержит содержит ссылку
первого общего предка этих двух типов. Такой предок (и, следовательно, ссылка
) всегда
существует тип
является предком для всех классов и интерфейсных типов. Если стеки
операндов невозможно объединить, то проверка метода завершается аварийно.
Для объединения двух массивов локальных переменных сравниваются соответствующие пары
локальных переменных. Если два типа не являются идентичными (за исключением типов
то верификатор записывает, что локальная переменная содержит неиспользуемое значение. Если обе
локальные переменные содержат значения типа
, то объединённое состояние содержит
ссылку
на первого общего предка двух данных типов.
Если анализатор потоков данных прошёл по методу без сообщений об ошибках, то считается то метод
успешно проверен
-файл верификатором.
Некоторые инструкции и типы данных усложняют работу анализатора потоков данных. Мы рассмотрим
каждую из них более детально.
Значения с типами
и
обрабатываются по особенному в процессе проверки.
Во всех случаях, когда значение типа
или
записывается в локальную переменную с
индексом n, индекс n+1 специально помечается, что он зарезервирован для значения по индексу n и
более не должен использоваться как индекс локальной переменной. Любое значение, которое
хранилось в локальной переменной с индексом n+1, перестаёт быть доступным для использования.
Во всех случаях, когда значение записывается в локальную переменную с индексом n, индекс n-1
проверяется на предмет того, не является ли он второй частью переменной типа
или
Значения с типами
и
Если это так, то локальная переменная с индексом n-1 меняется так, чтобы показать, что она
содержит значение, не доступное для использования. Поскольку локальная переменная с индексом n
была перезаписана, то локальная переменная с индексом n-1 более не может представлять значение
типа
или
Работа со значениями типа
или
в стеке операндов проще; Верификатор работает с ними
как с единичными значениями в стеке. Например, код для проверки инструкции
(сложить два
значения типа
) проверяет, что верхние два элемента в стеке оба имеют тип
. При
вычислении размера стека операндов, значения с типами
и
имеют размер равный двум.
Инструкции, не привязанные типу и манипулирующие стеком операндов, должны работать с типами
и
как атомарными (и делать это прозрачно). Например, верификатор сообщает об
ошибке, если на вершине стека находится значение типа
и верификатор встречает
инструкцию
или
для работы со стеком. Инструкции
или
должны быть использованы
в этом случае.
Сознание нового экземпляра класса — это многошаговый процесс. Этот оператор:
i, j, k
может быть реализован следующим образом:
#1 // Выделить неинициализированную
// память для объекта myClass
// Сделать дубликат объекта в стеке операндов
// Записать в стек i
// Записать в стек j
// Записать в стек k
#5 // Вызвать myClass.
Эта последовательность инструкций помещает на вершину стека операндов только что созданный
объект. (Дополнительные примеры компилирования набора инструкций виртуальной машины Java
даны в главе 3, «Компиляция в код виртуальной машины Java»)
Методы, инициализирующие экземпляр, и только что созданные объекты
Инициализирующий метод экземпляра (см.
) класса
видит новый неинициализированный
объект как аргумент
в локальной переменной с индексом 0. Единственная операция, которую
метод может выполнить перед тем как он вызывает другой инициализирующий метод экземпляра для
или его непосредственного предка это присвоение значений полям, объявленным в
При выполнения анализа потока данных в методах, принадлежащих экземпляру, верификатор
инициализирует локальную переменную с индексом 0 так, чтобы она содержала экземпляр текущего
класса, либо, в случае инициализирующего метода экземпляра, локальная переменная с индексом 0
содержит специальный тип, сообщающий о том, что объект ещё не инициализирован. После того как
был вызван соответствующий метод инициализации экземпляра (из текущего класса или класса
предка) для данного объекта, все вхождения специального типа в модели верификатора в стеке
операндов и массиве локальных переменных заменяются на текущий класс. Верификатор отклоняет
код, который использует новый объект прежде, чем он был инициализирован либо инициализирует
объект несколько раз. В дополнение он гарантирует, что каждый нормальный возврат из метода
вызвал инициализирующий метод экземпляра либо в классе, которому принадлежит метод либо в
непосредственном предке.
Аналогично, специальный тип создаётся и записывается в стек операндов модели верификатора как
результат выполнения инструкции
виртуальной машины Java. Специальный тип указывает на
инструкцию, с помощью которой был создан экземпляр класса, и тип неинициализированного
экземпляра класса. Когда вызывается инициализирующий метод экземпляра, объявленный в ещё не
инициализированном экземпляре, то все вхождения специального типа заменяются на
инициализированный экземпляр класса. Это изменение в типе может повлечь за собой изменения в
типах и в других инструкциях при последующем анализе потока данных.
Номер инструкции необходимо хранить как часть специального типа, так как могут быть множество
пока ещё не инициализированных экземпляров классов в стеке операндов в одно и тоже время.
Например, последовательность инструкций виртуальной машины Java, которые реализуют код
могут в одно и тоже время иметь два не инициализированных экземпляра
в стеке
операндов. Когда в экземпляре класса вызывается инициализирующий метод, то заменяются только
те вхождения специального типа в стеке операндов и в массиве локальных переменных, которые
являются такими же объектами что и экземпляр класса.
Если специальный тип неинициализированного объекта объединён со специальным типом, отличным
от себя самого, то допустимая последовательность инструкций не должна иметь не
инициализированных объектов в стеке операндов или локальных переменных при возвратной ветви
программы либо в коде, защищённым обработчиком исключения или блоком
. В противном
случае не благонадёжный кусок кода может «обмануть» верификатор и заставить его думать, что
верификатор произвёл инициализацию экземпляра класса, когда и должен был это сделать, хотя на
самом деле инициализированный экземпляр класса был создан в предыдущем проходе цикла.
Для реализации конструкции
, компилятор языка программирования Java с версией 50.0
либо ниже может использовать средства реализации исключений совместно с двумя специальными
инструкциями
(«переход к подпрограмме») и
Исключения и блок
Код блока
представляет собой отдельную проблему для верификатора. Обычно, если к
какой-нибудь инструкции можно перейти несколькими путями и при этом некоторая локальная
переменная содержит несовместимые значения из-за использования разных путей переход, то
локальная переменная становится неиспользуемой. Тем не менее блок
может быть вызван
из нескольких разных мест, что приводит к различным особенностям.
Вызов из обработчика исключений может привести к тому, что определённая локальная
переменная будет содержать исключение.
Вызов для реализации
Вызов при завершении блока
может привести к тому, что некоторая локальная переменная
будет содержать неопределённое значение.
Сам по себе код блока
может пройти проверку успешно, но после завершения обновления
всех последующих элементов инструкции
Каждая инструкция содержит список точек перехода
которые нужны, чтобы перейти к данной
инструкции. Для большинства случаев этот список пуст. Для инструкций внутри блока
размер равен одному. Для вложенных блоков
(очень редкая ситуация!) его размер может
быть более чем один.
Для каждой инструкции а каждого перехода
хранится битовый вектор, показывающий была ли
модифицирована либо выбрана из памяти локальная переменная, соответствующая индексу
вектора после выполнения
При выполнении инструкции
Используется специальная процедура для выполнения анализа потока данных инструкции
Для всех локальных переменных, для которых битовый вектор (созданный ранее) показывает,
что к переменной был доступ или её модификация, необходимо использовать тип локальной
переменной времени выполнения инструкции
Для всех остальных локальных переменных использовать тип локальной переменной перед
инструкцией
Следующие ограничения виртуальной машины Java являются следствием формата
Константный пул класса или интерфейса ограничен 65535 элементами из-за размерности 16 бит
поля
(см.
). Этот факт выступает в роли
внутреннего ограничения сложности класса или интерфейса.
Количество полей, которые можно объявить в классе или интерфейсе не превосходит 65535 и
ограничено размером элемента
(см.
Обратите внимание, что значение элемента
не включает в
себя поля, которые были унаследованы от классов предков или классов интерфейсов.
Количество методов, которые можно объявить в классе или интерфейсе не превосходит 65535 и
ограничено размером элемента
Количество непосредственных интерфейсов-предков класса или интерфейса не превосходит 65535
и ограничено размером элемента
(см.
Максимальное число локальных переменных в массиве локальных переменных фрейма, созданного
при вызове метода (см.
) не превышает 65535 и ограничено размером элемента
атрибута
(см.
), содержащего код метода, а также размером в 16 бит индекса
локальных переменных набора инструкций виртуальной машины Java.
Обратите внимание, что значения с типами
и
каждое рассматривается как
занимающее две локальных переменных и, следовательно, уменьшающее на два значение
, так что использование локальных переменных данных типов, сверх ещё более
уменьшает их максимально допустимое количество.
Размер стека операндов во фрейме (см.
) и ограничен размером элемента
атрибута
(см.
Обратите внимание, что значения с типами
и
каждое рассматривается как
занимающее два элемента
, так что использование локальных переменных данных
типов, сверх ещё более уменьшает их максимально допустимое количество.
Количество параметров метода не превышает 255 и ограничено определением дескриптора метода
(см.
), причём указанный предел включает в себя один элемент для параметра
случае вызова метода экземпляра или интерфейса.
Обратите внимание, что дескриптор метода определён с использованием понятий размера
переменной для которых параметр типа
или
занимает две единицы длинны, так что
Ограничения виртуальной машины Java
использование параметров метода данных типов, сверх ещё более уменьшает их максимально
допустимое количество.
Длинна имени поля или метода, дескриптора поля или метода и других строковых констант
(включая тех, на которые ссылается атрибут
)) не превышает 655535
символов и ограничена размерность 16 бит беззнакового элемента
(см.
Обратите внимание, что ограничение связано с числом байт в кодируемой строке, а не с числом
символов в строке. UTF-8 кодирует некоторые символы используя два или три байта. Поэтому
строки, включающие в себя многобайтовые символы ещё более ограничены по размеру.
Количество измерений многомерного массива не превышает 255 и ограничено размером операнда
инструкции
multianewarray
и ограничениями, налагаемыми инструкциями
multianewarray
,
и
(см.
и
Виртуальная машина Java динамически загружает, компонует и инициализирует классы и интерфейсы.
Загрузка это процесс поиска двоичного представления класса или интерфейсного типа с заданным
именем, а также создание класса или интерфейса из этого двоичного представления. Компоновка –
это процесс преобразования класса или интерфейса в состояние виртуальной машины Java времени
выполнения. Инициализация класса или интерфейса состоит в выполнении инициализирующего
метода
(см.
) класса или интерфейса.
В этой главе в параграфе
описано как виртуальная машина Java получает символьные ссылки на
класс или интерфейс из их двоичного представления. В
описаны процессы загрузки, компоновки
и инициализации, в случае, когда виртуальная машина Java выполняет их в первый раз за время
работы программы. В
описано как двоичное представление классов и интерфейсов загружается
загрузчиком классов и как создаются классы и интерфейсы. В
представлено описание процесса
компоновки. В
описаны подробности инициализации классов и интерфейсов. В
основные принципы связывания платформенно зависимых методов. Наконец, в
описаны условия
завершения работы виртуальной машины Java.
ГЛАВА 5. Загрузка, компоновка и инициализация
Константный пул времени выполнения
Виртуальная машина Java поддерживает работу типизированного константного пула (см.
) –
структуры данных времени выполнения, которая используется в общепринятой реализации языка
программирования. Константный пул используется как символьная таблица.
Таблица
(см.
) в двоичном представлении класса или интерфейса используется
для построения константного пула времени выполнения во время создания класса или интерфейса.
Все ссылки в константном пуле времени выполнения – это символьные ссылки. Символьные ссылки
константного пула времени выполнения создаются из структур данных двоичного представления
класса или интерфейса следующим образом:
Символьная ссылка на класс или интерфейс создаётся на основе структуры
(см.
), содержащейся в двоичном представлении класса или
интерфейса. Такая ссылка даёт имя класса или интерфейса в той форме, в которой её возвращает
метод
Если класс не является массивом, то имя класса – это двоичное имя (см.
Если класс представляет собой массив размерности n, то имя класса начинается с n повторений
ASCII символа «[», за которым следует описание типа элементов массива:
Если тип элемента является примитивным, то в имени он представлен соответствующим
дескриптором (см.
В противном случае, если тип элемента это ссылочный тип, он представлен ASCII символом
«L» за которым следует двоичное имя типа элемента и ASCII символ «;».
Везде в этой главе, где мы ссылаемся на имя класса или интерфейса, данное имя следует понимать
как имя, возвращаемое методом
Символьная ссылка на поле класса или интерфейса создаётся на основе структуры
(см.
), содержащейся в двоичном представлении класса или
интерфейса. Такая ссылка даёт имя и дескриптор поля, а также символьную ссылку на класс или
интерфейс, в котором данное поле может быть найдено.
Символьная ссылка на метод класса создаётся на основе структуры
Символьная ссылка на метод интерфейса создаётся на основе структуры
Символьная ссылка на обработчик метода создаётся на основе структуры
Символьная ссылка на тип метода создаётся на основе структуры
Символьная ссылка на спецификатор узла вызова создаётся на основе структуры
(см.
), содержащейся в двоичном представлении класса
или интерфейса. Такая ссылка предоставляет:
символьную ссылку на обработчик метода, который служит начальным методом для инструкции
invokedynamic
набор символьных ссылок (на классы, типы методов и обработчики методов), строковые
литералы, константы времени выполнения (например, значения числовых примитивов),
которые служат статическими аргументами загрузочного метода;
имя метода и дескриптор метода.
В дополнение, некоторые величины времени выполнения, не являющиеся символьными ссылками,
создаются на основе элементов таблицы
Строковые литералы, представляющие собой ссылки (тип
) на экземпляр класса
, создаются на основе структуры
(см.
), содержащейся в
двоичном представлении класса или интерфейса. Структура
представляет собой последовательность символов (кодовых точек) Unicode, составляющую
строковый литерал.
Согласно требованиям языка программирования Java, равные строковые литералы (то есть
содержащие одинаковую последовательность символов) должны ссылаться на один и тот же
экземпляр класса
(JLS §3.10.5). К тому же, если метод
вызывается для
произвольной строки, то результатом должна быть ссылка (тип
) на экземпляр класса,
которая будет возвращена, если бы исходная строка была единым литералом. Поэтому следующее
выражение должно всегда быть истинным:
Для создания строкового литерала, виртуальная машина Java проверяет последовательность
символов из структуры
Если метод
уже был вызван ранее для экземпляра класса
содержащего последовательность символов Unicode идентичную последовательности в
структуре
, то результат создания строкового литерала – ссылка типа
на уже созданный ранее экземпляр класса
В противном случае, создаётся новый экземпляр класса
, содержащий
последовательность символов Unicode из структуры
; результатом
создания строкового литерала будет ссылка на экземпляр класса. Затем вызывается метод
у только что созданного экземпляра класса
Константы времени выполнения создаются на основе структур
,
и
(см.
,
содержащихся в двоичном представлении класса или интерфейса.
Обратите внимание, что структура
представляет значения с плавающей
точкой одинарной точности согласно стандарту
754, а структура
двойной точности (см.
,
). Поэтому константы времени выполнения, созданные из этих
структур, должны быть значениями представимыми в стандарте
754 в одинарной и двойной
точности соответственно.
Оставшиеся структуры в таблице
в двоичном представлении класса или интерфейса

(см.
) и
(см.
) – используются
неявным образом при создании символьных ссылок на классы, интерфейсы, методы, поля, типы
методов и обработчики методов, а также при создании строковых литералов и спецификаторов узлов
Запуск виртуальной машины происходит с созданием инициализирующего класса, определённого в
начальном загрузчике классов (см.
) способом, зависящим от платформы. Затем виртуальная
машина Java компонует инициализирующий класс, инициализирует его и вызывает публичный метод
[]). С вызова этого метода начинается выполнение всех последующих
методов. Выполнение инструкций виртуальной машины Java, содержащихся в методе
, может
привести к компоновке (и, следовательно, созданию) дополнительных классов и интерфейсов, а также
вызову дополнительных методов.
В реализации виртуальной машины Java инициализирующий класс может быть задан как аргумент
командой строки. В качестве альтернативы, реализация виртуальной машины может предоставить
инициализирующий класс, который запускает загрузчик классов, который в свою очередь загружает
приложение. Также допустимы и другие варианты определения инициализирующего класса, если они
не противоречат спецификации, данной в предыдущем параграфе.
Создание класса или интерфейса
с именем
состоит из создания внутреннего представления класса
или интерфейса
в области методов виртуальной машины Java (см.
). Создание класса или
интерфейса инициируется другим классом или интерфейсом
, который ссылается на
константный пул времени выполнения.
Запуск виртуальной машины
Создание и загрузка
Создание класса или интерфейса может также быть инициировано, если
вызывает методы в
определённых библиотеках Java (см.
), таких как рефлексия.
Если
не является массивом, то загрузчик классов загружает его двоичное представление (см.
Массивы не имеют внешнего двоичного представления; они создаются виртуальной машиной Java, а
не загрузчиком классов.
Существуют две разновидности загрузчиков классов: начальный загрузчик классов, предоставляемый
виртуальной машиной Java и пользовательские загрузчики классов. Каждый пользовательский
загрузчик классов представляет собой экземпляр наследника абстрактного класса
Приложения используют пользовательские загрузчики классов, для изменения способа загрузки
классов, каким виртуальная машина Java загружает и, следовательно, создаёт классы.
Пользовательские загрузчики классов могут быть использованы для загрузки классов из
специфических источников. Например, класс может быть загружен из сети, сгенерирован во время
выполнения работы программы или извлечён из зашифрованного файла.
Загрузчик классов
может создать класс
непосредственно или путём вызова другого загрузчика
классов. Если
создаёт
непосредственно, то говорят, что
, или, что эквивалентно,
– это
определяющий загрузчик
Когда загрузчик классов вызывает другой загрузчик, то вызывающий загрузчик не обязательно также
и завершит загрузку и создание класса. Если
создаёт
или непосредственно или через вызов
другого загрузчика, то говорят, что
инициирует загрузку
или, что эквивалентно,
– это
инициирующий загрузчик
Во время выполнения, класс или интерфейс не определяется однозначно своим именем. Для
однозначного определения необходимо двоичное имя (см.
) и загрузчик класса. Каждый такой
класс или интерфейс принадлежит одному и только одному
пакету времени выполнения
. Пакт
времени выполнения класса или интерфейса определяется именем пакета и загрузчиком класса или
Виртуальная машина Java использует один из трёх способов создания класса или интерфейса
именем
Если
не является массивом, то используется один из следующих методов загрузки и,
следовательно, создания
Если
(класс или интерфейс, который ссылается на

прим. перев.
) создан начальным
загрузчиком, то именно он начинает загрузку
(см.
Если
создан пользовательским загрузчиком, то он же и начинает загрузку
(см.
В противном случае
является массивом. Массивы создаются непосредственно виртуальной
машиной Java (см.
), а не загрузчиком классов. Тем не менее, используется определяющий
загрузчик для создания массива
Если возникает ошибка во время загрузки класса, тогда должен быть создан экземпляр наследника
класса
и выброшен соответствующая ошибка, в точке программы, в которой
используется (явно или не явно) загружаемый класс или интерфейс.
Если виртуальная машина Java пытается загрузить класс
, во время процесса проверки (см.
или разрешения (см.
) (но не инициализации (см.
)) и при этом загрузчик классов,
загружающий
, выбрасывает исключение
, то виртуальная машина Java
должна выбросить исключение
, причиной которого является
(Если говорить более детально, здесь как часть процесса разрешения используется рекурсивный
вызов загрузчиков классов для создания иерархии наследования (см.
, шаг 3). Поэтому
исключение
, возникшее при загрузке класса-предка, должно быть
обёрнуто в
Примечание. Хорошо спроектированный загрузчик классов, должен удовлетворять следующим условиям:
Для одного и того же имени класса, хорошо спроектированный загрузчик классов,
должен всегда возвращать объект класса
Если загрузчик классов
для загрузки класса
вызывает другой загрузчик
, тогда
для любого типа
, который является
либо непосредственным классом предком или интерфейсом предком
либо типом поля в классе
либо типом формального параметра в методе или конструкторе класса
либо типом возвращаемого значения в классе
и
должны возвращать один и
тот же объект класса
Если пользовательский загрузчик классов выполняет упреждающую выборку двоичного
представления классов или интерфейсов либо загружает групп связанных классов, то он
должен сгенерировать ошибку в той точке программы, в которой она была бы, если бы
упреждающей или групповой загрузки не было.
Иногда мы будем обозначать класс или интерфейс с помощью нотации <
,
>, где
– это имя
класса или интерфейса, а
– определяющий загрузчик класса или интерфейса (от англ.

прим. перев.
Мы также будем обозначать класс или интерфейс как
, где
– это имя класса или интерфейса, а
– инициализирующий загрузчик класса или интерфейса (от англ.

Для загрузки и, следовательно, создания класса или интерфейса
с именем
с помощью начального
загрузчика классов необходимо выполнить следующие шаги.
В начале виртуальная машина Java определяет, был ли вызван начальный загрузчик в качестве
инициирующего для класса или интерфейса с именем
. Если да, то в создании класса
необходимости - он уже создан.
В противном случае виртуальная машина Java передаёт
в качестве аргумента начальному
загрузчику классов для поиска корректного платформенно зависимого двоичного представления
класса
. Обычно представлением класса или интерфейса является файл в иерархической файловой
системе, причём имя класса или интерфейса будет получено из имени файла.
Обратите внимание, что нет никаких гарантий того, что найденное представление будет корректным
или что оно соответствует классу
. Задача данной фазы загрузки выявить следующую ошибку:
Если корректного представления для класса
не найдено, то загрузчик классов генерирует
исключение
Затем виртуальная машина Java пытается создать класс с именем
из корректного двоичного
представления при помощи начального загрузчика по алгоритмам, описанным в
. Созданный
класс и есть
Для загрузки и, следовательно, создания класса или интерфейса
с именем
с помощью
пользовательского загрузчика классов
необходимо выполнить следующие шаги.
Вначале виртуальная машина Java определяет, был ли вызван пользовательский загрузчик
качестве инициирующего для класса или интерфейса с именем
. Если да, то в создании класса
необходимости - он уже создан.
В противном случае виртуальная машина Java вызывает у загрузчика
метод
). Этот
метод возвращает созданный класс или интерфейс. Затем виртуальная машина Java помечает, что
инициирующий загрузчик для
(см. 5.3.4). Ниже процесс описан более детально.
Когда вызывается метод
загрузчика
с параметром
для загрузки класса
, то загрузчик
должен выполнить одну из следующих двух операций:
.
Загрузчик
должен либо сам получить последовательность байт, представляющих класс
соответствии с форматом структуры
(см.
). После загрузчик вызывает метод
класса
. Вызов
приводит к тому, что виртуальная
Загрузка с помощью начального загрузчика классов
Загрузка с помощью пользовательского загрузчика классов
машина Java создаёт класс или интерфейс с именем
при помощи загрузчика
последовательности байт по алгоритму, описанному в
.
Либо загрузчик
должен делегировать загрузку класса
другому загрузчику
'. Это
достигается с помощью явной или не явной передачи аргумента
одному из методов
загрузчика
' (обычно это метод
). В результате создаётся класс
Как в случае выполнения первой операции, так и в случае выполнения второй если загрузчик классов
, не может загрузить класс или интерфейс с именем
по любой из причин, то он генерирует
исключение
Примечание. Начиная с JDK release 1.1, реализация виртуальной машины Java компании Oracle
использует метод
загрузчика классов для выполнения загрузки класса или интерфейса. В
качестве аргумента методу
передаётся имя класса или интерфейса. Также существует версия
метода
с двумя аргументами, где второй аргумент - это значение типа
, которое
показывает, нуждается ли класс или интерфейс в компоновке или нет. В JDK release 1.0.2 поддерживался
метод только с двумя аргументами, и реализация виртуальной машины Java компании Oracle
использовала этот аргумент для запуска процесса компоновки. Начиная с JDK release 1.1 и далее,
реализация виртуальной машины Java компании Oracle запускает процесс компоновки самостоятельно,
вне зависимости от загрузчика классов.
Для создания массива
с именем
с помощью загрузчика классов
выполняются следующие шаги.
Причём загрузчик
может быть либо начальным, либо пользовательским загрузчиком.
Если
уже помечен как инициирующий загрузчик для массива с таким же типом компонентов как у
то в создании класса
нет необходимости - он уже создан.
В противном случае для создания
выполняются следующие шаги:
.
Если тип компонентов – ссылочный тип (
), то рекурсивно применяется алгоритм из
для загрузчика классов
, что приведёт к загрузке и созданию класса, являющегося типом
компонента массива
.
Виртуальная машина Java создаёт новый массив с указанным выше типом компонентов и
заданной размерностью.
Если тип компонентов – ссылочный тип (
), то массив
помечается как тот, который
создаётся определяющим загрузчиком, соответствующим типу его компонент. В противном случае
Создание массивов
массив
помечается как тот, который создаётся начальным загрузчиком.
В любом случае виртуальная машина Java помечает
как инициирующий загрузчик для
Если тип компонентов – ссылочный тип (
), то права доступа к массиву, определяются
правами доступа к его типу его компонент. В противном случае доступ к массиву -
При наличии различных загрузчиков классов обеспечение безопасной с точки зрения типов
компоновки требует специальных мер. Может возникнуть ситуация, когда два различных загрузчика
класса начинают загрузку класса или интерфейса с именем
, причём имя
может означать
различный класс или интерфейс в каждом из загрузчиков.
Когда класс или интерфейс
ссылается на метод или поле другого класса или интерфейса
, то символьная ссылка включает в себя дескриптор, определяющий тип поля или тип
возвращаемого значения и типы аргументов метода. Важно, что любой тип с именем
либо как тип поля, либо как один из типов аргументов или тип возвращаемого значения означает
один и тот же класс или интерфейс, и когда загружается загрузчиком
и когда –
Чтобы гарантировать это виртуальная машина Java реализует ограничения загрузки в виде
во время подготовки (
) и разрешения (
). Виртуальная машина Java в заданные моменты
времени (см.
,
,
и
), помечает, что определённый загрузчик является
инициирующим загрузчиком определённого класса. После установления пометки о том, что загрузчик
является инициирующим загрузчиком класса, виртуальная машина Java должна проверить, не
нарушено ли какое-нибудь ограничение загрузки. Если это так, то пометка снимается, и виртуальная
машина Java генерирует
, и загрузка, приведшая к установлению пометки,
Ситуация, описанная выше, может произойти только во время процесса проверки ограничений
загрузки виртуальной машиной Java. Ограничение загрузки нарушено тогда и только тогда, когда все
четыре условия ниже истинны:
Существует загрузчик классов
такой, что
помечен виртуальной машиной Java как
инициирующий загрузчик класса
с именем
Существует загрузчик классов
' такой, что
' помечен виртуальной машиной Java как
инициирующий загрузчик класса
' с именем
Равенство
=
, вытекающее из описанных выше условий, истинно

Ограничения загрузки
Примечание. Полное обсуждение загрузчиков классов и выходит за рамки данной спецификации. Для
более полного освещения вопроса читатель отсылается к работе Шенга Лайанга и Гилада Брача
«Динамическая загрузка классов в виртуальной машине Java» (
Dynamic Class Loading in the Java Virtual
Machine
by Sheng Liang and Gilad Bracha
(Proceedings of the 1998 ACM SIGPLAN Conference on Object-
Oriented Programming Systems, Languages and Applications
Для создания класса или интерфейса
с именем
при помощи загрузчика
из корректного
файла необходимо выполнить следующие шаги:
1. Вначале виртуальная машина Java определяет есть ли пометка для загрузчика
о том что это
инициирующий загрузчик класса или интерфейса с именем
. Если пометка есть, то создание
прекращается и генерируется
2. В противном случае виртуальная машина Java пытается прочесть двоичное представление
класса. Однако, гарантий того, что представление будет правильным представлением класса
На этом этапе загрузки должны быть выявлены следующие ошибки:
Если представление не соответствует структуре
(см.
,
), то процесс
загрузки останавливается с ошибкой
Если представление относится к версии (более ранней или более поздней), которая не
поддерживается (см.
), то процесс загрузки останавливается с ошибкой
Примечание. Класс
, являющийся наследником
был создан для более точной идентификации ошибки, возникающей при неверном формате
представления класса, а именно: не поддерживаемой версии формата
-файла. В JDK release 1.1 и
ранее, в данном случае генерировался экземпляр
или
зависимости от того, загружался ли класс системным или пользовательским загрузчиком классов.
Если представление не относится к классу с именем
, то процесс загрузки останавливается
с ошибкой
либо с экземпляром класса-наследника данной ошибки.
Создание класса на основе данных
-файла
3. Если
имеет предка, то символьная ссылка из
на его непосредственного предка разрешается
согласно алгоритму, описанного в
. Обратите внимание, что если
- это интерфейс, то в
качестве класса предка должен быть класс
, который к этому времени должен быть уже
загружен. Только
не имеет непосредственного предка.
Любая ошибка, которая может быть сгенерирована в ходе разрешения класса или интерфейса,
может быть получена на данном этапе загрузки. К тому же на данном этапе загрузки должна быть
вывялена следующая ошибка:
Если предком загружаемого класса или интерфейса является интерфейс, то генерируется
ошибка
4. Если
является интерфейсом и имеет интерфейса-предка, то символьная ссылка на этот
интерфейс разрешается по алгоритму, описанному в
Любая ошибка, которая может быть сгенерирована в ходе разрешения класса или интерфейса,
может быть получена на данном этапе загрузки. К тому же на данном этапе загрузки должна быть
вывялена следующие ошибки:
Если предок-интерфейс интерфейса
не является на самом деле интерфейсом, то
генерируется ошибка
Если предок-интерфейс интерфейса
является самим интерфейсом
, генерируется ошибка
5. Виртуальная машина Java помечает загрузчик
как определяющий и инициирующий загрузчик
классов для
Компоновка класса или интерфейса включает в себя проверку и подготовку класса или интерфейса,
его непосредственного класса-предка, непосредственного интерфейса предка (при необходимости) а
также подготовку типов элементов, если речь идёт о массиве. Разрешение символьных ссылок на
класс или интерфейс является необязательной частью компоновки.
Данная спецификация допускает гибкость в реализации методов компоновки (и загрузки, поскольку
она также происходит при компоновке) при условии, что все перечисленные ниже условия
Класс или интерфейс полностью загружен перед компоновкой
Класс или интерфейс полностью проверен и подготовлен перед инициализацией.
Ошибки, обнаруженные во время компоновки, генерируются в той точке программы, где явно или
неявно программой выполнено действие, требующее компоновки класса или интерфейса,
вызвавшего ошибку.
Например, реализация виртуальной машины Java может разрешать каждую символьную ссылку на
класс или интерфейс только при первом её использовании («ленивое» или «позднее» разрешение)
либо сразу после того как класс был проверен («жадное» или «статическое» разрешение). Это
означает, что для некоторых реализаций виртуальной машины Java процесс разрешения может
продолжаться после того как класс или интерфейс был инициализирован. Какая бы не была стратегия
избрана, любая ошибка, обнаруженная во время разрешения, должна быть сгенерирована в точке
программы, которая явно или не явно использует символьную ссылку на класс или интерфейс.
Поскольку при компоновке выделяется память для новых структур данных, то компоновка может
завершиться ошибкой
Процесс
(см.
) гарантирует, что двоичное представление класс или интерфейса имеет
корректную структуру (см.
). Проверка может потребовать загрузки дополнительных классов или
интерфейсов (см.
) однако, процесс проверки или подготовки для дополнительно загруженных
классов проходить не будет.
Если двоичное представление класса или интерфейса не удовлетворяет статическим или структурным
ограничениям, приведённым в
, то должна быть сгенерирована ошибка
в той точке
программы, в которой необходимо было проверить класс или интерфейс.
Если попытка выполнить проверку класса или интерфейса закончилась аварийно по причине
возникновения ошибки
(или наследника), то последующие попытки выполнить
проверку класса или интерфейса также должны завершаться аварийно, причём с той же ошибкой
Процесс
включает в себя создание статических полей для класса или интерфейса и их
инициализацию значениями по умолчанию (
,
). Это не требует выполнения кода виртуальной
машины Java; код, инициализирующий значения статических полей выполняется как часть процесса
инициализации (см.
), а не подготовки.
Во время подготовки класса или интерфейса
виртуальная машина Java также проверяет выполнение
ограничений загрузки (см.
). Пусть
– определяющий загрузчик для
. Для каждого метода
объявленного в
, который замещает (см.
) метод, объявленный в классе-предке или
интерфейсе <
,
> виртуальная машина Java проверяет выполнение следующих ограничений:
Обозначим тип возвращаемого значения метода
как
, а типы формальных параметров как
, ...,
. Тогда:
Если
не является массивом, то пусть
будет конкретным типом
. В противном случае
обозначим через Если
тип элемента массива (см.
), который и будет конкретным типом
Цикл по
.
=1 до
: Если
не является массивом, то пусть
будет конкретным типом
. В
противном случае обозначим через
тип элемента массива (см.
), который и будет конкретным
типом
Тогда при
= 0 до
справедливо
Более того, если
имплементирует метод
, объявленный в интерфейсе-предке <
,
>, при этом
непосредственно не содержит метода
, тогда обозначим через <
,
> класс-предок
, который
содержит реализацию метода
. Виртуальная машина Java проверяет выполнение следующих
Обозначим тип возвращаемого значения метода
как
, а типы формальных параметров как
, ...,
. Тогда:
Если
не является массивом, то пусть
будет конкретным типом
. В противном случае
обозначим через
тип элемента массива (см.
), который и будет конкретным типом
Цикл по
.
=1 до
: Если
не является массивом, то пусть
будет конкретным типом
. В
противном случае обозначим через
тип элемента массива (см.
), который и будет конкретным
типом
Тогда при
= 0 до
справедливо
Инструкции виртуальной машины Java
,
,
такая же символьная ссылка уже разрешена для любой другой инструкции
invokedynamic
Для всех остальных инструкций выше (кроме
invokedynamic
) разрешение символьной ссылки для
одной из инструкций подразумевает, что ссылка будет разрешена и для остальных инструкций.
(Имеется в виду, что конкретное значение объекта узла вызова, определённое при разрешении
инструкции
invokedynamic
будет привязано именно к этой инструкции
invokedynamic
Попытка разрешения может быть выполнена для символьной ссылки, которая уже разрешена.
Попытка разрешения символьной ссылки, которая уже успешно разрешена, приводит к тому, что в
качестве результата используется элемент, найденный при первоначальном разрешении.
Если в ходе разрешения символьной ссылки возникает ошибка, то должен быть сгенерирован
экземпляр (или наследник)
в той точке программы, которая (явно
или не явно) использует символьную ссылку.
Если попытка виртуальной машины Java разрешить символьную ссылку завершается аварийно,
вследствие ошибки
(или наследника), то последующие попытки разрешения ссылки
закончатся также аварийно, причём с такой же ошибкой, которая была при первоначальном
Символьная ссылка на спецификатор узла вызова, связанная с определённой инструкцией
invokedynamic
не должна разрешаться до выполнения этой инструкции.
В случае неуспешного разрешения символьной ссылки у инструкции
invokedynamic
, загрузочный метод
не выполняется повторно при последующих попытках разрешения.
Некоторые из инструкций выше требуют дополнительных компоновочных проверок при разрешении
символьных ссылок. Например, для того, чтобы выполнить инструкцию
Для разрешения не разрешённой ещё символьной ссылки из
на класс или интерфейс
с именем
выполняются следующие шаги:
.
Определяющий загрузчик из
используется для создания класса или интерфейса именем
Этот класс или интерфейс есть
. Детали процесс представлены в
. Любые исключения,
которые могут быть сгенерированы в результате ошибки при создании класса или интерфейса,
могут, следовательно, быть выброшенными и при разрешении класса или интерфейса.
.
Если
– это массив с компонентами ссылочного типа (тип
), тогда символьная
ссылка на класс или интерфейс, представляющий тип элемента разрешается при помощи
рекурсивного алгоритма
.
Затем проверяются права доступа к
Если к
нет доступа (см.
) из
, при разрешении класса или интерфейса генерируется
Примечание. Это может произойти, например, если у
– класса, изначально объявленного с
модификатором
, был изменён доступ на не-
после компиляции
Если шаги 1 и 2 были выполнены успешно, а шаг 3 – нет, то
действителен и доступен к
использованию. Тем не менее, разрешение завершается аварийно и из
запрещён доступ к
Для разрешения неразрешенной символьной ссылки из
на поле класса или интерфейса
, сначала
должна быть разрешена символьная ссылка из
на сам класс
(см.
). Поэтому любые
исключения, которые могут быть сгенерированы в результате аварийного разрешения класса или
интерфейса, могут быть выброшены в процессе разрешения поля. Если ссылка на
разрешена, то, тем не менее, могут быть выброшены исключения, связанные с разрешением поля как
Когда происходит разрешение символьной ссылки на поле, то виртуальная машина Java вначале
пытается искать поле в классе
и его предках:
Разрешение классов и интерфейсов
Разрешение поля
.
Если
содержит объявление поля с именем и дескриптором, которые совпадают с искомыми,
то процесс поиска завершается. Объявленное поле и есть результат поиска.
.
В противном случае, поиск рекурсивно продолжается в интерфейсах-предках, которые
имплементирует данный класс или наследует интерфейс
.
Если
имеет предка класс
, то поиск продолжается рекурсивно в
и классах предках.
.
Если поле к этому моменту не найдено, то поиск завершается аварийно.
Если поле найдено, генерируется исключение
Если поле найдено, но к нему нет доступа из
(см.
), генерируется исключение
В противном случае пусть <
,
> - это класс или интерфейс, содержащий поле, на которое
ссылается
, и пусть
– это определяющий загрузчик
Тогда обозначим тип поля как
, и пусть
будет равным
, если
– не является массивом. В
противном случае, если
– массив, пусть
будет типом элемента массива (см.
Виртуальная машина Java гарантирует выполнение следующего ограничения загрузки:
(см.
Для разрешения неразрешенной ещё символьной ссылки из
на метод в классе
разрешается символьная ссылка на класс
(см.
). Поэтому любые исключения, которые могут
быть сгенерированы в результате аварийного разрешения класса или интерфейса, могут быть
выброшены в процессе разрешения метода. Если ссылка на
успешно разрешена, то, тем не менее,
могут быть выброшены исключения, связанные с разрешением метода как такового.
При разрешении символьной ссылки на метод происходит следующее:
1. Процедура разрешения проверяет, является ли
классом или интерфейсом.
Если
– интерфейс, то генерируется исключение
2. Процедура разрешения ищет метод в классе
и его предках:
Если в
объявлен только один метод с именем, определяемым ссылкой, и его объявление
сигнатурно полиморфное (см.
), то поиск метода завершён. Разрешаются все имена
классов, упомянутые в дескрипторе метода (см.
Разрешенный метод имеет сигнатурно полиморфное объявление. Для класса
обязательно иметь объявление метода с дескриптором, определяемым ссылкой на метод.
Разрешение метода
В противном случае, если в
объявлен метод с именем и дескриптором таким же, как и имя
и дескриптор, определяемый по ссылке, то поиск метода завершён.
В противном случае, если у
есть класс-предок, то шаг 2 повторяется для
непосредственного класса-предка
3. Процедура разрешения ищет метод в интерфейсах-предках класса
Если в интерфейсе-предке
объявлен метод с именем и дескриптором таким же, как и имя
и дескриптор, определяемый по ссылке, то поиск метода завершён.
В противном случае, поиск метода завершён аварийно.
Если поиск метода завершён аварийно, процесс разрешения генерирует
Если поиск метода завершён успешно и найденный метод абстрактный, но класс
не абстрактный,
то процесс разрешения генерирует
Если поиск метода завершён успешно, но найденный метод не доступен (см.
) для
, то
процесс разрешения генерирует
В противном случае пусть <
,
> - это класс или интерфейс, содержащий метод
, на который
ссылается
и пусть
будет определяющим загрузчиком для
Возвращаемый тип метода
обозначим как
, а типы формальных параметров
обозначим через
, ...,
. Тогда:
Если
не является массивом, то пусть
будет конкретным типом
. В противном случае
обозначим через
тип элемента массива (см.
), который и будет конкретным типом
Цикл по
.
=1 до
: Если
не является массивом, то пусть
будет конкретным типом
. В
противном случае обозначим через
тип элемента массива (см.
), который и будет
конкретным типом
Тогда при
= 0 до
справедливо
(см.
Для разрешения неразрешенной ещё символьной ссылки из
на метод в интерфейсе
разрешается символьная ссылка на интерфейс
(см.
). Поэтому любые исключения, которые
могут быть сгенерированы в результате аварийного разрешения интерфейса, могут быть выброшены в
процессе разрешения метода интерфейса. Если ссылка на
успешно разрешена, то, тем не менее,
могут быть выброшены исключения, связанные с разрешением интерфейсного метода как такового.
При разрешении символьной ссылки на метод в интерфейсе происходит следующее:
Процедура разрешения проверяет, является ли
классом или интерфейсом. Если
– не
Разрешение метода интерфейса
интерфейс, то генерируется исключение
В противном случае, если разрешаемый метод не имеет такого же дескриптора, как и метод в
интерфейсе
или в интерфейсах предках
или в классе
, процедура разрешения
генерирует
В противном случае пусть <
,
> - это класс или интерфейс, содержащий интерфейсный метод
на который ссылается
и пусть
будет определяющим загрузчиком для
Возвращаемый тип метода
обозначим как
, а типы формальных параметров
обозначим через
, ...,
. Тогда:
Если
не является массивом, то пусть
будет конкретным типом
. В противном случае
обозначим через
тип элемента массива (см.
), который и будет конкретным типом
Цикл по
.
=1 до
: Если
не является массивом, то пусть
будет конкретным типом
. В
противном случае обозначим через
тип элемента массива (см.
), который и будет
конкретным типом
Тогда при
= 0 до
справедливо
(см.
Для разрешения неразрешенной ещё символьной ссылки из
на тип метода разрешаются все
символьные ссылки на классы, упомянутые в дескрипторе метода, находящемся в типе метода (см.
). Поэтому любые исключения, которые могут быть сгенерированы в результате аварийного
разрешения ссылок на классы, могут быть выброшены в процессе разрешения типа метода.
Результатом разрешения типа метода является ссылка (типа
) на экземпляр класса
Разрешение типов методов и обработчиков методов
putfield
putstatic
invokevirtual
invokestatic
invokespecial
;
;
invokespecial
invokeinterface
Обозначим через
символьную ссылку на обработчик метода, которая в данный момент
разрешается (см.
). Тогда
Пусть
- символьная ссылка на поле или метод, содержащиеся в
создаётся на основе одной из структур:
,
Пусть
- символьная ссылка на тип, на который ссылается
создаётся на основе структуры
, на которую ссылается элемент
в одной из структур:
,
Пусть
и
будут соответственно именем поля и метода, на который ссылается
и
создаются на основе структуры
, на которую ссылается элемент
в одной из структур:
,
Пусть
и
* (в случае метода) будут возвращаемым типом и последовательность типов входных
аргументов, на которые ссылается
и
* создаются на основе структуры
, на которую ссылается элемент
в одной из структур:
,
экземпляр класса
ссылки на дескриптор метода (см.
) в зависимости от разновидности обработчика метода
(см. Таблицу 5.2).
Таблица 5.2 Дескрипторы методов для обработчиков методов
Код разновидности
Дескриптор метода
Примечание. Класс
обработчиков методов без байт-кода с эквивалентным поведением. Их поведение определяется методом
Получить из спецификатора узла вызова символьную ссылку на обработчик метода, который
служит
начальным методом
для динамического узла вызова. Обработчик метода разрешается (см.
) для получения ссылки на экземпляр
Получить из спецификатора узла вызова символьную ссылку на дескриптор метода
Формируется ссылка на экземпляр
Получить из спецификатора узла вызова ноль или более
статических аргументов
, которые
представляют собой зависящие от приложения метаданные для начального метода. Все
статические аргументы, которые представляют собой символьные ссылки на классы, обработчики
методов или типы методов разрешаются как при вызове инструкции
ldc
для получения ссылок на
объекты типа
,
ссылки (тип
) на экземпляр класса
ссылки (тип
) на экземпляр класса
набора ссылок (тип
) на экземпляры классов
Разрешение спецификатора узла вызова
Класс или интерфейс
доступен для класса или интерфейса
тогда и только тогда, когда хотя бы
одно из следующих условий истинно:
объявлен с модификатором
и
члены одного и того же пакета времени выполнения (см.
Поле или метод
доступны для класса или интерфейса
тогда и только тогда, когда хотя бы одно из
следующих условий истинно:
объявлен с модификатором
объявлен с модификатором
в классе
, а
является либо классом наследником
либо
и
- это один и тот же класс. Более того, если метод
не статический, тогда символьная
ссылка на *
должна содержать символьную ссылку на класс
, причём
должен быть либо
классом наследником
, либо классом предком
либо совпадать с
объявлен с модификатором
или имеет доступ по умолчанию (то есть не
, не
и не
) и объявлен в классе, находящимся в одном пакете с
объявлен в
с модификатором
Обсуждение управления доступом не включает в себя проверку того, что если поле или метод
объявлены с модификатором
в
, то класс
либо совпадает с
либо является
наследником
. Это условие проверяется при выполнении процесса проверки (см.
) а не
Метод экземпляра
, объявленный в классе
замещает другой метода экземпляра
, объявленный
в классе
, тогда и только тогда, когда все следующие условия удовлетворены:
является наследником
имеет такое же имя и дескриптор, как и
Справедливо одно из следующих утверждений:
помечен как
; либо
помечен как
; либо
не помечен ни
как
ни как
, ни как
и принадлежит тому же пакету
Управление доступом
Замещение методов
что и
, либо
замещает метод
, при этом
отличается от
,
отличается от
так, что
Инициализация
класса или интерфейса заключается в выполнении его инициализирующего метода
(см.
Класс или интерфейс может быть инициализирован только в следующих случаях:
В результате выполнения инструкций
,
В результате первого вызова экземпляра
В результате вызова некоторых методов рефлексии в библиотеке классов (см.
), например, в
классе
или пакете
В результате инициализации одного из потомков класса.
В результате начальной загрузки виртуальной машины Java (см.
До инициализации класс или интерфейс должен быть скомпонован, то есть, проверен, подготовлен и
в некоторых случаях разрешён.
Поскольку виртуальная машина Java работает в многопоточной среде, инициализация класса или
интерфейса требует тщательной синхронизации, другой поток может пытаться инициализировать один
и тот же класс или интерфейс в одно и то же время. Также возможно, что инициализация класса или
интерфейса может быть вызвана рекурсивно как часть инициализации этого же класса или
интерфейса. Реализация виртуальной машины Java для корректной инициализации класса или
интерфейса использует следующую процедуру. Виртуальная машина Java предполагает, что экземпляр
класса
уже проверен и подготовлен и содержит в себе флаг состояние, который может
принимать четыре значения, соответствующие ситуациям:
Экземпляр класса
проверен и подготовлен, но не инициализирован.
Экземпляр класса
инициализируется в данный момент каким-либо другим потоком.
Экземпляр класса
полностью инициализирован и готов к использованию.
Экземпляр класса
пребывает в состоянии ошибки, возможно потому, что попытка
инициализации завершилась аварийно.
Для каждого класса или интерфейса
существует уникальная инициализирующая блокировка
Установление соответствия между
и
зависит от имплементации виртуальной машины Java.
Например,
может быть экземпляром класса
для
или некоторым монитором для
экземпляра класса
. Процедура инициализации класса
состоит из следующих шагов:
1. Синхронизироваться по инициализирующей блокировке
класса
. Этот процесс включает в
себя ожидание до тех пор, пока текущий поток не сможет захватить блокировку
2. Если объект класса
(соответствующий
) в данный момент инициализируется другим
потоком (определяется по флагу состояния для экземпляра класса
), то текущий поток не
захватывает
, а ждёт до тех пор, пока не будет информирован о том, что находящаяся в
процессе работы инициализация завершена. После чего текущий поток повторяет попытку захвата
3. Если объект класса
(соответствующий
) в данный момент инициализируется текущим
потоком, значит, происходит рекурсивная инициализация. Блокировка
освобождается и
инициализация завершается.
4. Если объект класса
(соответствующий
) уже инициализирован, тогда в последующих
действиях нет необходимости. Блокировка
освобождается и инициализация завершается.
5. Если объект класса
(соответствующий
) находится в состоянии ошибки, то дальнейшая
инициализация невозможна. Блокировка
освобождается и генерируется исключение
6. В противном случае факт того, что инициализация экземпляра класса
) началась текущим потоком, отмечается во флаге состояния. Блокировка
Затем инициализируются все поля класса
, объявленные с модификаторами
установленным атрибутом
в порядке их появления в структуре
7. Затем, если
это класс, а не интерфейс и его предок
еще не был инициализирован, то вся
процедура инициализации повторяется рекурсивно для
. Если необходимо, сначала происходит
проверка и подготовка
Если инициализация
завершается аварийно из-за выброшенного исключения, то блокировка
захватывается, флаг состояния для экземпляра класса
(соответствующего
устанавливается в значение, указывающее об ошибке, посылается уведомление всем ждущим
потокам, блокировка
освобождается, а инициализация завершается аварийно с тем же
исключением, которое было выброшено при инициализации
8. Затем выясняется, были ли включены утверждения для
, с помощью его определяющего
загрузчика классов.
9. Затем выполняется инициализирующий метод класса или интерфейса
10. Если выполнение инициализирующего метода класса или интерфейса завершается успешно,
тогда блокировка
захватывается, флаг состояния для экземпляра класса
(соответствующего
) устанавливается в значение, указывающее на успешное завершение
инициализации, посылается уведомление всем ждущим потокам, блокировка
инициализация завершается успешно.
11. В противном случае инициализация класса или интерфейса завершилась аварийно с некоторым
исключением
. Если не принадлежит классу
или одному из его потомков, то создаётся
экземпляр класса
, а в качестве аргумента конструктора при
создании используется
. Затем экземпляр класса
в следующем шаге.
Если экземпляр
не может быть создан вследствие ошибки
, то в следующем шаге вместо
12. Блокировка
захватывается, флаг состояния для экземпляра класса
(соответствующего
) устанавливается в значение, указывающее об ошибке, посылается
уведомление всем ждущим потокам, блокировка
освобождается, а инициализация завершается
аварийно с тем же исключением
, либо тем, что замещает его, как было определено в
предыдущем шаге.
В реализации виртуальной машины Java допустимо оптимизировать эту процедуру, пропустив захват
блокировки в шаге 1 (и освобождение в шагах 4 и 5), когда виртуальная машина Java может
определить, что инициализация классов уже успешно завершена. При этом в терминах модели памяти
Java все ребра
(см. JLS §17.4.5), которые существовали при захвате блокировки и
так существуют.
Связывание – это процесс в ходе, которого функция, написанная на языке отличном от Java и
реализованная как платформенно зависимый (
) метод, интегрируется в виртуальную машину
Java так, что может быть выполнена. Хотя традиционно этот процесс называют компоновкой, термин
связывание используется в данной спецификации для различения при описании компоновки классов и
интерфейсов и компоновки платформенно зависимых методов.
Связывание платформенно зависимых методов
Виртуальная машина Java завершает работу, когда один из потоков вызывает метод
или класса
, либо метод
класса
и при этом выполнение операций
и
разрешено менеджером безопасности.
Кроме того спецификация
(Java
) описывает завершение работы виртуальной
машины Java, если используется
вызовов из
для загрузки и выгрузки виртуальной машины
Инструкция виртуальной машины Java состоит из кода операции, обозначающего действие, которое
будет выполнено и набора операндов (могут отсутствовать вообще), с которыми производится
действие. В этой главе приведено детальное описание формата каждой из инструкций виртуальной
машины Java и действия, которое она выполняет.
Описание каждой инструкции всегда выполнено в контексте кода виртуальной машины Java, который
удовлетворяет статическим и структурным ограничениям, описанным в
. В описании конкретной
инструкции виртуальной машины Java мы часто полагаем, что некоторое условие «должно» или «не
должно» быть выполнено, например: значение2 должно быть типа
. Ограничения, описанные в
, обеспечивают истинность всех допущений. Если некоторое условие («должно» или «не должно») в
описании инструкции не выполнено во время выполнения, то поведение виртуальной машины Java не
Виртуальная машина Java проверяет, используя верификатор
-файла (см.
), что байт-код
виртуальной машины удовлетворяет статическим и структурным ограничениям во время линковки
кода. Поэтому виртуальная машина Java совершает попытку выполнения только
-файлов,
Завершение работы виртуальной машины
ГЛАВА 6. Набор инструкций виртуальной машины Java
Допущения: значение слова «обязательный»
прошедших проверку. Проверка
-файла во время линковки удобна тем, что она выполняется
только один раз, существенно уменьшая количество работы, которую необходимо совершить во время
выполнения кода. Однако и другие подходы также возможны, при условии, что будут соблюдены
требования
Спецификации языка программирования Java
и
Спецификации виртуальной машины Java
В дополнение к кодам операций, которым в
-файле соответствуют инструкции виртуальной
машины Java, зарезервировано три кода операции для внутреннего использования виртуальной
машиной. Если в будущем набор инструкций виртуальной машины Java будет расширен,
зарезервированные коды не будут использоваться в качестве новых команд, и сохранят свой смысл.
Два зарезервированных байт-кода со значениями (254 (
) и 255 (
)) имеют мнемоники
impdep1
и
impdep2
соответственно. Эти инструкции предоставляют «лазейку» виртуальной машине
Java для реализации функциональности зависящей от программного и аппаратного обеспечения
соответственно. Третий зарезервированный байт-код (202 (
)) имеет мнемоническое обозначение
breakpoint
и используется отладчиками для реализации точек останова.
Не смотря на то, что описанные выше байт-коды являются зарезервированными, их допустимо
использовать только «внутри» реализации виртуальной машины Java. В
-файле они
использоваться не могут. Такие инструменты как отладчик или
-компилятор (динамический
компилятор, см.
), непосредственно взаимодействующие с загруженным и уже исполняемым
кодом виртуальной машины Java, могут использовать эти байт-коды. И отладчик и
-компилятор
должны корректно обрабатывать зарезервированные байт-коды.
Если во время работы виртуальной машины Java происходит внутренняя ошибка либо нехватка
ресурсов, то виртуальная машина Java выбрасывает исключение
: Произошла внутренняя ошибка в реализации виртуальной машины Java либо по
причине программной ошибки непосредственно в реализации, либо в программном обеспечении
Зарезервированные коды операций
Ошибки виртуальной машины
платформы, на которой установлена Java, либо в аппаратном обеспечении. Эта ошибка
генерируется асинхронно (см.
) и может произойти в любой точке программы.
: Реализация виртуальной машины Java запросила памяти (виртуальной или
физической) больше чем доступно и система автоматического управления памятью не смогла
выполнить запрос на выделение.
: Реализация виртуальной машины Java запросила памяти для стека,
принадлежащего потоку, более чем доступно. Обычно это происходит из-за ошибки в рекурсивном
выполнении методов.
: Произошла ошибка или исключительная ситуация, но реализация виртуальной
машины Java не в состоянии определить точную причину ошибки.
В этой главе представлено описание инструкций в следующей форме в алфавитном порядке, каждая
инструкция на отдельной странице.
Краткое описание инструкции
= код операции
Стек операндов
...,
значение1
,
значение2
...,
значение3
Подробное описание, содержащее ограничения стека операндов, элементы
константного пула, тип возвращаемого результата и так далее.
Если возможно возникновение исключений связывания во время компоновки
программы, то они приводятся в этом разделе в порядке их вызова.
Формат описания инструкций
Если возможно возникновение исключений времени выполнения инструкции, то
они приводятся в этом разделе в порядке их вызова.
Инструкции не могут генерировать иные исключения, кроме как исключений
связывания, времени выполнения и
многоточием (…), остаются неизменными в ходе выполнения инструкции.
Каждое из значения типов
и
представляет собой единый элемент в стеке операндов.
Примечание. В первой редакции
Спецификации виртуальной машины Java
каждое из значений операндов
с типами
и
представляли собой два элемент в стеке операндов.
Список инструкций JVM с детальным описанием формата и действий, которые они выполняют
Эта глава содержит таблицу соответствия между мнемоническими инструкциями и их байт-кодами
(включая зарезервированные байт-коды, см.
) виртуальной машины Java. Байт-код со значением
186 не использовался до Java SE 7.
iload
istore
aconst_null
lload
lstore
iconst_m1
fload
iconst_0
iconst_1
aload
iconst_2
iload_0
istore_0
iconst_3
iload_1
istore_1
iconst_4
iload_2
istore_2
iconst_5
iload_3
istore_3
lconst_0
lload_0
lstore_0
lconst_1
lload_1
lstore_1
lload_2
lstore_2
lload_3
lstore_3
fload_0
fload_1
fload_2
ГЛАВА 7 Таблица инструкций по возрастанию их байт-кодов
fload_3
sipush
ldc
ldc_w
ldc2_w
aload_0
aload_1
aload_2
aload_3
iaload
iastore
laload
lastore
faload
daload
aaload
baload
caload
saload
Преобразования типов
iadd
i2l
ladd
i2f
i2d
l2i
isub
l2f
lsub
l2d
imul
lmul
idiv
i2b
ldiv
i2c
fdiv
i2s
ddiv
irem
lrem
ineg
lneg
ishl
lshl
ishr
lshr
iushr
lushr
iand
Политика конфиденциальности
Описание JavaCogito
Отказ от ответственности
land
ior
lor
ixor
lxor
iinc
Работа со ссылками
lcmp

Приложенные файлы

  • pdf 9548789
    Размер файла: 1 006 kB Загрузок: 0

Добавить комментарий