Spec-Zone.ru › Java Virtual Machine Specification 8

Глава 5. Загрузка, связывание и инициализация

Оглавление

5.1. Пул постоянных времени выполнения
5.2. Запуск виртуальной машины Java
5.3. Создание и загрузка
5.3.1. Загрузка с помощью загрузчика начального класса
5.3.2. Загрузка с помощью пользовательского загрузчика классов
5.3.3. Создание классов массивов
5.3.4. Ограничения загрузки
5.3.5. Получение класса из представления файла class
5.4. Связывание
5.4.1. Верификация
5.4.2. Подготовка
5.4.3. Разрешение
5.4.3.1. Разрешение классов и интерфейсов
5.4.3.2. Разрешение полей
5.4.3.3. Разрешение методов
5.4.3.4. Разрешение методов интерфейса
5.4.3.5. Разрешение типов методов и дескрипторов методов
5.4.3.6. Разрешение спецификаторов места вызова
5.4.4. Управление доступом
5.4.5. Переопределение
5.5. Инициализация
5.6. Связывание реализаций методов нативных методов
5.7. Выход из виртуальной машины Java

Виртуальная машина Java динамически загружает, связывает и инициализирует классы и интерфейсы. Загрузка — это процесс поиска двоичного представления типа класса или интерфейса с определённым именем и создание класса или интерфейса по этому двоичному представлению. Связывание — это процесс объединения класса или интерфейса в состояние времени выполнения виртуальной машины Java, чтобы его можно было выполнить. Инициализация класса или интерфейса состоит из выполнения метода инициализации класса или интерфейса <clinit> (§2.9).

В этой главе §5.1 описывает, как виртуальная машина Java получает символические ссылки из двоичного представления класса или интерфейса. §5.2 объясняет, как процессы загрузки, связывания и инициализации впервые инициируются виртуальной машиной Java. §5.3 определяет, как двоичные представления классов и интерфейсов загружаются загрузчиками классов и как создаются классы и интерфейсы. Связывание описано в §5.4. §5.5 подробно описывает, как инициализируются классы и интерфейсы. §5.6 вводит понятие связывания нативных методов. Наконец, §5.7 описывает, когда виртуальная машина Java завершает работу.

5.1. Пул констант во время выполнения

Виртуальная машина Java поддерживает пул констант (§2.5.5), структуру данных во время выполнения, которая выполняет многие функции таблицы символов в реализации традиционных языков программирования.

Таблица constant_pool (§4.4) в двоичном представлении класса или интерфейса используется для построения пула констант во время выполнения при создании класса или интерфейса (§5.3). Все ссылки в пуле констант изначально символические. Символические ссылки в пуле констант выводятся из структур в двоичном представлении класса или интерфейса следующим образом:

  • Символическая ссылка на класс или интерфейс выводится из структуры CONSTANT_Class_info (§4.4.1) в двоичном представлении класса или интерфейса. Такая ссылка содержит имя класса или интерфейса в форме, возвращаемой методом Class.getName, а именно:

    • Для не-массивнового класса или интерфейса имя является двоичным именем (§4.2.1) класса или интерфейса.

    • Для массива класса с n измерениями имя начинается с n вхождений символа ASCII "[" и далее представляет тип элемента:

      • Если тип элемента является примитивным типом, он представляется соответствующим описателем поля (§4.3.2).

      • В противном случае, если тип элемента является ссылочным типом, он представляется символом ASCII "L", за которым следует двоичное имя (§4.2.1) типа элемента и символом ASCII ";".

    В этом разделе под именем класса или интерфейса понимается форма, возвращаемая методом Class.getName.

  • Символическая ссылка на поле класса или интерфейса выводится из структуры CONSTANT_Fieldref_info (§4.4.2) в двоичном представлении класса или интерфейса. Такая ссылка содержит имя и описатель поля, а также символическую ссылку на класс или интерфейс, в котором находится поле.

  • Символическая ссылка на метод класса выводится из структуры CONSTANT_Methodref_info (§4.4.2) в двоичном представлении класса или интерфейса. Такая ссылка содержит имя и описатель метода, а также символическую ссылку на класс, в котором находится метод.

  • Символическая ссылка на метод интерфейса выводится из структуры CONSTANT_InterfaceMethodref_info (§4.4.2) в двоичном представлении класса или интерфейса. Такая ссылка содержит имя и описатель метода интерфейса, а также символическую ссылку на интерфейс, в котором находится метод.

  • Символическая ссылка на обработчик методов выводится из структуры CONSTANT_MethodHandle_info (§4.4.8) в двоичном представлении класса или интерфейса. Такая ссылка указывает на символическую ссылку на поле класса или интерфейса, метод класса или метод интерфейса в зависимости от типа обработчика.

  • Символическая ссылка на тип метода выводится из структуры CONSTANT_MethodType_info (§4.4.9) в двоичном представлении класса или интерфейса. Такая ссылка содержит описатель метода (§4.3.3).

  • Символическая ссылка на спецификатор места вызова выводится из структуры CONSTANT_InvokeDynamic_info (§4.4.10) в двоичном представлении класса или интерфейса. Такая ссылка содержит:

    • символическую ссылку на обработчик метода, который будет служить методом загрузки для инструкции invokedynamic (§invokedynamic);

    • последовательность символических ссылок (на классы, типы методов и обработчики методов), строковые литералы и значения констант во время выполнения, которые будут служить статическими аргументами для метода загрузки;

    • имя метода и описатель метода.

Кроме того, некоторые значения во время выполнения, которые не являются символическими ссылками, выводятся из элементов, найденных в таблице constant_pool:

  • Строковый литерал является ссылкой на экземпляр класса String и выводится из структуры CONSTANT_String_info (§4.4.3) в двоичном представлении класса или интерфейса. Структура CONSTANT_String_info задаёт последовательность точек кода Юникода, составляющих строковый литерал.

    Язык программирования Java требует, чтобы идентичные строковые литералы (то есть литералы, содержащие одну и ту же последовательность точек кода) ссылались на один и тот же экземпляр класса String (JLS §3.10.5). Кроме того, если вызывается метод String.intern для любой строки, результатом является ссылка на тот же экземпляр класса, который был бы возвращён, если бы эта строка появилась как литерал. Таким образом, следующее выражение должно иметь значение true:

    ("a" + "b" + "c").intern() == "abc"
        

    Для получения строкового литерала виртуальная машина Java исследует последовательность точек кода, заданную структурой CONSTANT_String_info.

    • Если метод String.intern был ранее вызван для экземпляра класса String, содержащего последовательность точек кода Юникода, идентичную той, что задана структурой CONSTANT_String_info, то результатом получения строкового литерала является ссылка на этот же экземпляр класса String.

    • В противном случае создаётся новый экземпляр класса String, содержащий последовательность точек кода Юникода, заданную структурой CONSTANT_String_info; ссылка на этот экземпляр класса является результатом получения строкового литерала. Наконец, вызывается метод intern нового экземпляра String.

  • Значения констант во время выполнения выводятся из структур CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info или CONSTANT_Double_info (§4.4.4, §4.4.5) в двоичном представлении класса или интерфейса.

    Обратите внимание, что структуры CONSTANT_Float_info представляют значения в формате IEEE 754 с одинарной точностью, а структуры CONSTANT_Double_info представляют значения в формате IEEE 754 с двойной точностью (§4.4.4, §4.4.5). Значения констант во время выполнения, полученные из этих структур, должны соответствовать значениям, которые могут быть представлены в формате IEEE 754 с одинарной и двойной точностью соответственно.

Остальные структуры в таблице constant_pool двоичного представления класса или интерфейса — структуры CONSTANT_NameAndType_info и CONSTANT_Utf8_info (§4.4.6, §4.4.7) — используются косвенно только при получении символических ссылок на классы, интерфейсы, методы, поля, типы методов и обработчики методов, а также при получении строковых литералов и спецификаторов места вызова.

5.2. Загрузка виртуальной машины Java

Виртуальная машина Java начинает работу, создавая начальный класс, который определяется способом, зависящим от реализации, с помощью загрузчика начального класса (§5.3.1). Затем виртуальная машина Java связывает начальный класс, инициализирует его и вызывает метод класса public метод void main(String[]). Вызов этого метода управляет дальнейшим выполнением. Выполнение инструкций виртуальной машины Java, составляющих метод main, может привести к связыванию (и, следовательно, созданию) дополнительных классов и интерфейсов, а также вызову дополнительных методов.

В реализации виртуальной машины Java начальный класс может быть передан в качестве аргумента командной строки. В качестве альтернативы, реализация может предоставить начальный класс, который настраивает загрузчик классов, который в свою очередь загружает приложение. Другие варианты начального класса возможны, при условии их соответствия спецификации, приведенной в предыдущем абзаце.

5.3. Создание и загрузка

Создание класса или интерфейса C с именем N включает построение в области методов виртуальной машины Java (§2.5.4) внутреннего представления C, специфичного для реализации. Создание класса или интерфейса инициируется другим классом или интерфейсом D, который ссылается на C через свою постоянную таблицу символов. Создание класса или интерфейса может также быть вызвано D вызывающим методы определённых библиотек классов платформы Java SE (§2.12), таких как рефлексия.

Если C не является классом массива, он создаётся путём загрузки двоичного представления C (§4 (Формат файла class)) с помощью загрузчика классов. Классы массивов не имеют внешнего двоичного представления; они создаются виртуальной машиной Java, а не загрузчиком классов.

Существуют два типа загрузчиков классов: загрузчик начального класса, поставляемый виртуальной машиной Java, и пользовательские загрузчики классов. Каждый пользовательский загрузчик классов является экземпляром подкласса абстрактного класса ClassLoader. Приложения используют пользовательские загрузчики классов для расширения способа динамической загрузки и, следовательно, создания классов виртуальной машиной Java. Пользовательские загрузчики классов могут использоваться для создания классов, происходящих из пользовательских источников. Например, класс может быть загружен из сети, сгенерирован динамически или извлечён из зашифрованного файла.

Загрузчик классов L может создать C, определив его непосредственно или делегировав другому загрузчику классов. Если L создаёт C непосредственно, мы говорим, что L определяет C или, что то же самое, что L является загрузчиком определения C.

Когда один загрузчик классов делегирует другому загрузчику классов, загрузчик, инициирующий загрузку, не обязательно тот же загрузчик, который завершает загрузку и определяет класс. Если L создаёт C, либо непосредственно, либо путём делегирования, мы говорим, что L инициирует загрузку C или, что то же самое, что L является инициирующим загрузчиком C.

Во время выполнения класс или интерфейс определяется не только по имени, но и по паре: его двоичное имя (§4.2.1) и его загрузчик определения. Каждый такой класс или интерфейс принадлежит к одному временному пакету. Временной пакет класса или интерфейса определяется именем пакета и загрузчиком определения класса или интерфейса.

Виртуальная машина Java использует один из трёх методов для создания класса или интерфейса C с обозначением N:

  • Если N обозначает класс или интерфейс, не являющийся массивом, для загрузки и создания C используется один из двух методов:

    • Если D был определён загрузчиком начального класса, то загрузчик начального класса инициирует загрузку C (§5.3.1).

    • Если D был определён пользовательским загрузчиком, то тот же самый пользовательский загрузчик инициирует загрузку C (§5.3.2).

  • В противном случае N обозначает класс массива. Класс массива создаётся непосредственно виртуальной машиной Java (§5.3.3), а не загрузчиком классов. Однако загрузчик определения D используется в процессе создания класса массива C.

Если при загрузке класса произошла ошибка, то экземпляр подкласса LinkageError должен быть выброшен в точке программы, которая (прямо или косвенно) использует загружаемый класс или интерфейс.

Если виртуальная машина Java пытается загрузить класс C во время проверки (§5.4.1) или разрешения (§5.4.3) (но не инициализации (§5.5)), и загрузчик классов, используемый для инициации загрузки C, выбрасывает экземпляр ClassNotFoundException, то виртуальная машина Java должна выбросить экземпляр NoClassDefFoundError, причиной которого является экземпляр ClassNotFoundException.

(Здесь есть нюанс: рекурсивная загрузка классов для загрузки суперклассов выполняется как часть разрешения (§5.3.5, шаг 3). Поэтому ошибка ClassNotFoundException, возникшая из-за того, что загрузчик классов не смог загрузить суперкласс, должна быть обернута в NoClassDefFoundError.)

Хороший загрузчик классов должен поддерживать три свойства:

  • При одинаковом имени, хороший загрузчик классов всегда должен возвращать тот же объект Class.

  • Если загрузчик классов L1 делегирует загрузку класса C другому загрузчику L2, то для любого типа T, являющегося непосредственным суперклассом или непосредственным суперинтерфейсом C, или в качестве типа поля в C, или как тип формального параметра метода или конструктора в C, или как возвращаемый тип метода в C, L1 и L2 должны возвращать тот же объект Class.

  • Если пользовательский загрузчик предварительно загружает двоичные представления классов и интерфейсов или загружает группу связанных классов вместе, то он должен отражать ошибки загрузки только в тех местах программы, где они могли бы возникнуть без предварительной загрузки или групповой загрузки.

Иногда мы будем представлять класс или интерфейс с помощью обозначения <N, Ld>, где N обозначает имя класса или интерфейса, а Ld обозначает загрузчик определения класса или интерфейса.

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

5.3.1. Загрузка с использованием загрузчика начального класса

Следующие шаги используются для загрузки и создания класса или интерфейса C, обозначенного N, с помощью загрузчика начального класса.

Во-первых, виртуальная машина Java определяет, был ли загрузчик начального класса уже записан в качестве загрузчика инициирования класса или интерфейса с обозначением N. Если да, то этот класс или интерфейс есть C, и создание класса не требуется.

В противном случае виртуальная машина Java передает аргумент N в вызов метода загрузчика начального класса, чтобы найти предполагаемое представление C платформозависимым образом. Как правило, класс или интерфейс будет представлен файлом в иерархической файловой системе, а имя класса или интерфейса будет закодировано в пути к файлу.

Обратите внимание, что нет гарантии, что найденное предполагаемое представление является действительным или является представлением C. Этот этап загрузки должен обнаруживать следующую ошибку:

  • Если предполагаемое представление C не найдено, загрузка выбрасывает экземпляр ClassNotFoundException.

Затем виртуальная машина Java пытается получить класс, обозначенный N, с помощью загрузчика начального класса из предполагаемого представления с использованием алгоритма, указанного в §5.3.5. Этот класс и есть C.

5.3.2. Загрузка с помощью пользовательского загрузчика классов

Следующие шаги используются для загрузки и, следовательно, создания не массива класса или интерфейса C, обозначаемого N, с помощью пользовательского загрузчика классов L.

Сначала виртуальная машина Java определяет, был ли L уже записан как инициализирующий загрузчик класса или интерфейса, обозначаемого N. Если да, то этот класс или интерфейс — C, и создание класса не требуется.

В противном случае виртуальная машина Java вызывает loadClass(N) на L. Возвращаемое значение вызова — созданный класс или интерфейс C. Затем виртуальная машина Java записывает, что L является инициализирующим загрузчиком C (§5.3.4). Остальная часть этого раздела описывает этот процесс более подробно.

Когда метод loadClass загрузчика классов L вызывается с именем N класса или интерфейса C, который должен быть загружен, L должен выполнить одну из следующих двух операций для загрузки C:

  1. Загрузчик классов L может создать массив байтов, представляющих C как байты структуры ClassFile (§4.1); затем он должен вызвать метод defineClass класса ClassLoader. Вызов defineClass заставляет виртуальную машину Java вывести класс или интерфейс, обозначаемый N, используя L из массива байтов в соответствии с алгоритмом, описанным в §5.3.5.

  2. Загрузчик классов L может делегировать загрузку C другому загрузчику классов L'. Это достигается путем передачи аргумента N непосредственно или косвенно в вызов метода на L' (обычно метод loadClass). Результатом вызова является C.

В любом из случаев (1) или (2), если загрузчик классов L не может загрузить класс или интерфейс, обозначаемый N по какой-либо причине, он должен сгенерировать исключение ClassNotFoundException.

Начиная с версии JDK 1.1, реализация виртуальной машины Java от Oracle вызывает метод loadClass загрузчика классов, чтобы вызвать загрузку класса или интерфейса. Аргументом для loadClass является имя класса или интерфейса, который нужно загрузить. Также существует версия метода loadClass с двумя аргументами, где второй аргумент — boolean, указывающий, нужно ли связывать класс или интерфейс или нет. Только версия с двумя аргументами была предоставлена в версии JDK 1.0.2, и реализация виртуальной машины Java от Oracle полагалась на неё для связывания загруженного класса или интерфейса. Начиная с версии JDK 1.1 и выше, реализация виртуальной машины Java от Oracle связывает класс или интерфейс напрямую, без использования загрузчика классов.

5.3.3. Создание массивов классов

Следующие шаги используются для создания массива класса C, обозначаемого N, с помощью загрузчика классов L. Загрузчик классов L может быть либо загрузчиком bootstrap, либо пользовательским загрузчиком классов.

Если L уже был записан как инициализирующий загрузчик массива класса с таким же типом компонента, как и N, этот класс — C, и создание массива класса не требуется.

В противном случае, для создания C выполняются следующие шаги:

  1. Если тип компонента — тип reference, алгоритм этого раздела (§5.3) применяется рекурсивно с использованием загрузчика классов L для загрузки и, следовательно, создания типа компонента C.

  2. Виртуальная машина Java создает новый массив класса с указанным типом компонента и числом измерений.

    Если тип компонента — тип reference, C помечается как определенный определяющим загрузчиком класса типа компонента. В противном случае C помечается как определенный загрузчиком bootstrap.

    В любом случае виртуальная машина Java затем записывает, что L является инициализирующим загрузчиком для C (§5.3.4).

    Если тип компонента — тип reference, доступность массива класса определяется доступностью его типа компонента. В противном случае доступность массива класса — public.

5.3.4. Ограничения загрузки

Обеспечение безопасной связи типов в присутствии загрузчиков классов требует особого внимания. Возможна ситуация, когда два разных загрузчика классов инициируют загрузку класса или интерфейса, обозначаемого N, и имя N может обозначать разные классы или интерфейсы в каждом загрузчике.

Когда класс или интерфейс C = <N1, L1> делает символическую ссылку на поле или метод другого класса или интерфейса D = <N2, L2>, символическая ссылка включает описание, определяющее тип поля или возвращаемые и аргументные типы метода. Важно, чтобы любое имя типа N, указанное в описании поля или метода, обозначало один и тот же класс или интерфейс при загрузке L1 и при загрузке L2.

Чтобы гарантировать это, виртуальная машина Java накладывает ограничения загрузки в виде NL1 = NL2 во время подготовки (§5.4.2) и разрешения (§5.4.3). Для принудительного выполнения этих ограничений, виртуальная машина Java в определенные моменты (см. §5.3.1, §5.3.2, §5.3.3 и §5.3.5), записывает, что определенный загрузчик является инициализирующим загрузчиком определенного класса. После записи того, что загрузчик является инициализирующим загрузчиком класса, виртуальная машина Java должна немедленно проверить, нарушены ли какие-либо ограничения загрузки. Если да, то запись отменяется, виртуальная машина Java генерирует исключение LinkageError, и операция загрузки, которая привела к записи, завершается неудачно.

Аналогично, после применения ограничения загрузки (см. §5.4.2, §5.4.3.2, §5.4.3.3 и §5.4.3.4), виртуальная машина Java должна немедленно проверить, нарушены ли какие-либо ограничения загрузки. Если да, то наложенное ограничение загрузки отменяется, виртуальная машина Java генерирует исключение LinkageError, и операция, которая привела к применению ограничения (либо разрешение, либо подготовка, в зависимости от ситуации), завершается неудачно.

Описанные здесь ситуации — единственные моменты, в которых виртуальная машина Java проверяет, нарушены ли какие-либо ограничения загрузки. Ограничение загрузки нарушается тогда и только тогда, когда выполняются следующие четыре условия:

  • Существует загрузчик L такой, что L был записан виртуальной машиной Java как инициализирующий загрузчик класса C с именем N.

  • Существует загрузчик L' такой, что L' был записан виртуальной машиной Java как инициализирующий загрузчик класса C ' с именем N.

  • Определение эквивалентности, заданное (транзитивным замыканием) набором наложенных ограничений, подразумевает NL = NL'.

  • C ≠ C '.

Полное обсуждение загрузчиков классов и безопасности типов выходит за рамки этой спецификации. Для более подробного обсуждения читатели могут обратиться к работе Динамическая загрузка классов в виртуальной машине Java Шенга Лянга и Гилада Брахи (Труды конференции ACM SIGPLAN 1998 по объектно-ориентированному программированию, системам, языкам и приложениям).

5.3.5. Вывод класса из представления файла класса class

Следующие шаги используются для вывода объекта Class для класса или интерфейса без массивов C, обозначенного N, с использованием загрузчика L из предполагаемого представления в формате файла class.

  1. Сначала виртуальная машина Java определяет, уже ли она записала, что L является инициализирующим загрузчиком класса или интерфейса, обозначенного N. Если да, эта попытка создания недействительна, и загрузка вызывает исключение LinkageError.

  2. В противном случае виртуальная машина Java пытается разобрать предполагаемое представление. Однако предполагаемое представление может на самом деле не быть допустимым представлением C.

    Этот этап загрузки должен обнаружить следующие ошибки:

    • Если предполагаемое представление не является структурой ClassFile (§4.1, §4.8), загрузка вызывает экземпляр ClassFormatError.

    • В противном случае, если предполагаемое представление не имеет поддерживаемой основной или дополнительной версии (§4.1), загрузка вызывает экземпляр UnsupportedClassVersionError.

      UnsupportedClassVersionError, подкласс ClassFormatError, был введен для облегчения идентификации ошибки ClassFormatError, вызванной попыткой загрузить класс, представление которого использует недопустимую версию формата файла class. В выпуске JDK 1.1 и более ранних версиях при недопустимой версии выбрасывался экземпляр NoClassDefFoundError или ClassFormatError, в зависимости от того, загружался ли класс системным загрузчиком или пользователем.

    • В противном случае, если предполагаемое представление не действительно представляет класс с именем N, загрузка вызывает экземпляр NoClassDefFoundError или экземпляр одного из его подклассов.

  3. Если у C есть непосредственный суперкласс, символическая ссылка от C к его непосредственному суперклассу разрешается с помощью алгоритма §5.4.3.1. Обратите внимание, что если C является интерфейсом, он должен иметь Object в качестве непосредственного суперкласса, который уже должен был загружен. Только Object не имеет непосредственного суперкласса.

    Любые исключения, которые могут быть выброшены из-за разрешения класса или интерфейса, могут быть выброшены на этом этапе загрузки. Кроме того, этот этап загрузки должен обнаружить следующие ошибки:

    • Если класс или интерфейс, указанный в качестве непосредственного суперкласса C, на самом деле является интерфейсом, загрузка вызывает IncompatibleClassChangeError.

    • В противном случае, если любой из суперклассов C является C самим по себе, загрузка вызывает ClassCircularityError.

  4. Если у C есть непосредственные суперинтерфейсы, символические ссылки от C к его непосредственным суперинтерфейсам разрешаются с помощью алгоритма §5.4.3.1.

    Любые исключения, которые могут быть выброшены из-за разрешения класса или интерфейса, могут быть выброшены на этом этапе загрузки. Кроме того, этот этап загрузки должен обнаружить следующие ошибки:

    • Если любой из классов или интерфейсов, указанных в качестве непосредственных суперинтерфейсов C, на самом деле не является интерфейсом, загрузка вызывает IncompatibleClassChangeError.

    • В противном случае, если любой из суперинтерфейсов C является C самим по себе, загрузка вызывает ClassCircularityError.

  5. Виртуальная машина Java помечает C как имеющий L в качестве определяющего загрузчика и записывает, что L является инициализирующим загрузчиком C (§5.3.4).

5.4. Связывание

Связывание класса или интерфейса включает проверку и подготовку этого класса или интерфейса, его непосредственного суперкласса, его непосредственных суперинтерфейсов и типа элемента (если это массивный тип), при необходимости. Разрешение символических ссылок в классе или интерфейсе является необязательной частью связывания.

Данный спецификация предоставляет реализации гибкость относительно того, когда выполняются действия связывания (и, из-за рекурсии, загрузки), при условии, что поддерживаются все следующие свойства:

  • Класс или интерфейс полностью загружается перед связыванием.

  • Класс или интерфейс полностью проверяется и подготавливается перед инициализацией.

  • Ошибки, обнаруженные во время связывания, выбрасываются в точке программы, где программа выполняет какое-либо действие, которое может прямо или косвенно потребовать связывания с классом или интерфейсом, участвующим в ошибке.

Например, реализация Java Virtual Machine может выбрать разрешение каждой символической ссылки в классе или интерфейсе индивидуально при её использовании («ленивое» или «позднее» разрешение) или разрешить их все сразу при проверке класса («жадное» или «статическое» разрешение). Это означает, что процесс разрешения может продолжаться в некоторых реализациях после инициализации класса или интерфейса. Какой бы стратегия ни была выбрана, любая ошибка, обнаруженная во время разрешения, должна быть сгенерирована в точке программы, которая (прямо или косвенно) использует символическую ссылку на класс или интерфейс.

Поскольку связывание включает выделение новых структур данных, оно может завершиться с ошибкой OutOfMemoryError.

5.4.1. Проверка

Проверка (§4.10) гарантирует, что двоичное представление класса или интерфейса структурно правильно (§4.9). Проверка может привести к загрузке дополнительных классов и интерфейсов (§5.3), но не обязательно к их проверке или подготовке.

Если двоичное представление класса или интерфейса не удовлетворяет статическим или структурным ограничениям, перечисленным в §4.9, то должна быть сгенерирована ошибка VerifyError в точке программы, которая вызвала проверку класса или интерфейса.

Если попытка Java Virtual Machine проверить класс или интерфейс завершается ошибкой, которая является экземпляром LinkageError (или подклассом), то последующие попытки проверки класса или интерфейса всегда завершатся той же ошибкой, которая была сгенерирована в результате первоначальной попытки проверки.

5.4.2. Подготовка

Подготовка включает создание статических полей для класса или интерфейса и инициализацию этих полей их значениями по умолчанию (§2.3, §2.4). Это не требует выполнения кода Java Virtual Machine; явные инициализаторы для статических полей выполняются в рамках инициализации (§5.5), а не подготовки.

Во время подготовки класса или интерфейса C, Java Virtual Machine также накладывает ограничения загрузки (§5.3.4). Пусть L1 будет определяющей загрузкой C. Для каждого метода m, объявленного в C, который переопределяет (§5.4.5) метод, объявленный в суперклассе или суперинтерфейсе <D, L2>, Java Virtual Machine накладывает следующие ограничения загрузки:

Учитывая, что тип возвращаемого значения m — Tr, а типы формальных параметров m — Tf1, ..., Tfn, то:

Если Tr не является массивом, пусть T0 будет Tr; в противном случае пусть T0 будет типом элемента (§2.4) Tr.

Для i = 1 до n: Если Tfi не является массивом, пусть Ti будет Tfi; в противном случае пусть Ti будет типом элемента (§2.4) Tfi.

Тогда TiL1 = TiL2 для i = 0 до n.

Кроме того, если C реализует метод m, объявленный в суперинтерфейсе <I, L3>, но C сам не объявляет метод m, то пусть <D, L2> — суперкласс C, который объявляет реализацию метода m, унаследованного C. Java Virtual Machine накладывает следующие ограничения:

Учитывая, что тип возвращаемого значения m — Tr, а типы формальных параметров m — Tf1, ..., Tfn, то:

Если Tr не является массивом, пусть T0 будет Tr; в противном случае пусть T0 будет типом элемента (§2.4) Tr.

Для i = 1 до n: Если Tfi не является массивом, пусть Ti будет Tfi; в противном случае пусть Ti будет типом элемента (§2.4) Tfi.

Тогда TiL2 = TiL3 для i = 0 до n.

Подготовка может произойти в любое время после создания, но должна быть завершена до инициализации.

5.4.3. Разрешение

Инструкции Java Virtual Machine anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield и putstatic используют символические ссылки на пул постоянных времени выполнения. Выполнение любой из этих инструкций требует разрешения её символической ссылки.

Разрешение — это процесс динамического определения конкретных значений по символическим ссылкам в пуле постоянных времени выполнения.

Разрешение символической ссылки одной инструкции invokedynamic не подразумевает, что та же символическая ссылка считается разрешённой для любой другой инструкции invokedynamic.

Для всех остальных инструкций выше разрешение символической ссылки одной инструкции подразумевает, что та же символическая ссылка считается разрешённой для любой другой инструкции, не являющейся invokedynamic.

(Вышеизложенное подразумевает, что конкретное значение, определённое разрешением для конкретной инструкции invokedynamic, является объектом сайта вызова, связанным с этой конкретной инструкцией invokedynamic.)

Разрешение может быть предпринято для символической ссылки, которая уже была разрешена. Попытка разрешить символическую ссылку, которая уже была успешно разрешена, всегда завершается успешно тривиальным образом и всегда приводит к той же сущности, что и первоначальное разрешение этой ссылки.

Если при разрешении символической ссылки возникает ошибка, то в программе в точке, которая (прямо или косвенно) использует символическую ссылку, должен быть сгенерирован экземпляр IncompatibleClassChangeError (или подкласс).

Если попытка Java Virtual Machine разрешить символическую ссылку терпит неудачу из-за исключения, которое является экземпляром LinkageError (или подкласса), то последующие попытки разрешить ссылку всегда терпят неудачу с той же ошибкой, которая была вызвана в результате первоначальной попытки разрешения.

Символическая ссылка на спецификатор сайта вызова, заданную конкретной инструкцией invokedynamic, не должна быть разрешена до выполнения этой инструкции.

В случае неудачного разрешения инструкции invokedynamic метод Bootstrap не выполняется повторно при последующих попытках разрешения.

Некоторые из указанных выше инструкций требуют дополнительных проверок связывания при разрешении символических ссылок. Например, для успешного разрешения инструкции getfield на символическую ссылку на поле, на котором она работает, она должна не только выполнить шаги разрешения поля, заданные в §5.4.3.2, но также проверить, что поле не является static. Если это static поле, необходимо сгенерировать исключение связывания.

Заметим, что для успешного разрешения инструкции invokedynamic на символическую ссылку на спецификатор сайта вызова, метод Bootstrap, указанный в нём, должен завершиться нормально и вернуть подходящий объект сайта вызова. Если метод Bootstrap завершится внезапно или вернёт непригодный объект сайта вызова, необходимо сгенерировать исключение связывания.

Исключения связывания, генерируемые проверками, специфичными для выполнения конкретной инструкции Java Virtual Machine, приведены в описании этой инструкции и не рассматриваются в этом общем обсуждении разрешения. Обратите внимание, что такие исключения, хотя и описаны как часть выполнения инструкций Java Virtual Machine, а не разрешения, всё же правильно рассматриваются как ошибки разрешения.

Следующие разделы описывают процесс разрешения символической ссылки в пуле постоянных времени выполнения (§5.1) класса или интерфейса D. Детали разрешения различаются в зависимости от типа разрешаемой символической ссылки.

5.4.3.1. Разрешение классов и интерфейсов

Для разрешения неразрешённой символической ссылки из D на класс или интерфейс C, обозначенный N, выполняются следующие шаги:

  1. Используется загрузчик определяющего класса D для создания класса или интерфейса, обозначенного N. Этот класс или интерфейс — C. Подробности процесса описаны в §5.3.

    Любое исключение, которое может быть вызвано в результате неудачи при создании класса или интерфейса, может быть вызвано в результате неудачи при разрешении класса и интерфейса.

  2. Если C — это класс массива, а его тип элемента — тип reference, то символическая ссылка на класс или интерфейс, представляющий тип элемента, разрешается путём рекурсивного вызова алгоритма в §5.4.3.1.

  3. Наконец, проверяются разрешения доступа к C.

    • Если C недоступен (§5.4.4) для D, разрешение класса или интерфейса выбрасывает исключение IllegalAccessError.

      Это условие может возникнуть, например, если C — это класс, который первоначально был объявлен public, но был изменён на не-public после того, как D был скомпилирован.

Если шаги 1 и 2 завершаются успешно, но шаг 3 завершается неудачей, C всё ещё валиден и пригоден для использования. Тем не менее, разрешение терпит неудачу, и D запрещено получать доступ к C.

5.4.3.2. Разрешение полей

Для разрешения неразрешённой символической ссылки из D на поле в классе или интерфейсе C, символическая ссылка на C, заданная ссылкой на поле, должна быть сначала разрешена (§5.4.3.1). Поэтому любое исключение, которое может быть вызвано в результате неудачи при разрешении ссылки на класс или интерфейс, может быть вызвано в результате неудачи при разрешении ссылки на поле. Если ссылка на C может быть успешно разрешена, может быть сгенерировано исключение, связанное с неудачей при разрешении самой ссылки на поле.

При разрешении ссылки на поле, разрешение поля сначала пытается найти указанное поле в C и его суперклассах:

  1. Если C объявляет поле с именем и описанием, указанными в ссылке на поле, поиск поля завершается успешно. Объявленное поле является результатом поиска поля.

  2. В противном случае поиск поля применяется рекурсивно к непосредственным суперинтерфейсам указанного класса или интерфейса C.

  3. В противном случае, если у C есть суперкласс S, поиск поля применяется рекурсивно к S.

  4. В противном случае поиск поля завершается неудачей.

Затем:

  • Если поиск поля завершается неудачей, разрешение поля выбрасывает исключение NoSuchFieldError.

  • В противном случае, если поиск поля завершается успешно, но указанное поле недоступно (§5.4.4) для D, разрешение поля выбрасывает исключение IllegalAccessError.

  • В противном случае пусть <E, L1> будет классом или интерфейсом, в котором фактически объявлено указанное поле, и пусть L2 будет загрузчиком определяющим D.

    Учитывая, что тип указанного поля — Tf, пусть T будет Tf, если Tf не является типом массива, и пусть T будет типом элемента (§2.4) Tf в противном случае.

    Java Virtual Machine должна наложить ограничение на загрузку, что TL1 = TL2 (§5.3.4).

5.4.3.3. Разрешение метода

Для разрешения неразрешенной символической ссылки из D на метод в классе C, сначала разрешается символическая ссылка на C, заданная ссылкой на метод (§5.4.3.1). Поэтому любое исключение, которое может быть выброшено в результате неудачи разрешения ссылки на класс, может быть выброшено в результате неудачи разрешения метода. Если ссылка на C может быть успешно разрешена, могут быть выброшены исключения, связанные с самим разрешением ссылки на метод.

При разрешении ссылки на метод:

  1. Если C является интерфейсом, разрешение метода выбросит IncompatibleClassChangeError.

  2. В противном случае разрешение метода пытается найти указанный метод в C и его суперклассах:

    • Если C объявляет ровно один метод с именем, указанным в ссылке на метод, и объявление является методом полиморфизма по сигнатуре (§2.9), поиск метода успешен. Все имена классов, упомянутые в описателе, разрешаются (§5.4.3.1).

      Разрешенный метод — это объявление метода полиморфизма по сигнатуре. Нет необходимости для C объявлять метод с описателем, указанным в ссылке на метод.

    • В противном случае, если C объявляет метод с именем и описателем, указанными в ссылке на метод, поиск метода успешен.

    • В противном случае, если C имеет суперкласс, шаг 2 разрешения метода рекурсивно вызывается для непосредственного суперкласса C.

  3. В противном случае, разрешение метода пытается найти указанный метод в суперинтерфейсах указанного класса C:

    • Если методы суперинтерфейса максимальной специфичности класса C для имени и описателя, указанных в ссылке на метод, включают ровно один метод, у которого не установлен флаг ACC_ABSTRACT, то этот метод выбирается, и поиск метода успешен.

    • В противном случае, если любой суперинтерфейс C объявляет метод с именем и описателем, указанными в ссылке на метод, у которого не установлен ни флаг ACC_PRIVATE, ни флаг ACC_STATIC, один из них произвольно выбирается, и поиск метода успешен.

    • В противном случае, поиск метода завершается неудачей.

Метод суперинтерфейса максимальной специфичности класса или интерфейса C для конкретного имени и описателя метода — это любой метод, для которого выполняются все следующие условия:

  • Метод объявлен в суперинтерфейсе (прямом или косвенном) C.

  • Метод объявлен с указанным именем и описателем.

  • У метода не установлен ни флаг ACC_PRIVATE, ни флаг ACC_STATIC.

  • Если метод объявлен в интерфейсе I, то не существует другого метода суперинтерфейса максимальной специфичности C с указанным именем и описателем, объявленного в подинтерфейсе I.

Результат разрешения метода определяется тем, удался ли поиск метода:

  • Если поиск метода завершился неудачей, разрешение метода выбросит NoSuchMethodError.

  • В противном случае, если поиск метода удался, а указанный метод недоступен (§5.4.4) для D, разрешение метода выбросит IllegalAccessError.

  • В противном случае, пусть <E, L1> — класс или интерфейс, в котором фактически объявлен указанный метод m, а L2 — загружающий модуль D.

    С учётом того, что тип возвращаемого значения m — Tr, а типы формальных параметров m — Tf1, ..., Tfn:

    Если Tr не является массивом, пусть T0 будет Tr; в противном случае, пусть T0 будет элементом типа (§2.4) Tr.

    Для i = 1 до n: Если Tfi не является массивом, пусть Ti будет Tfi; в противном случае, пусть Ti будет элементом типа (§2.4) Tfi.

    Виртуальная машина Java должна накладывать ограничения загрузки TiL1 = TiL2 для i = 0 до n (§5.3.4).

При поиске метода в суперинтерфейсах класса, наилучший результат — это идентификация метода максимальной специфичности, не являющегося abstract. Возможно, этот метод будет выбран при выборе метода, поэтому желательно добавить ограничения загрузчика классов для него.

В противном случае результат неопределён. Это не ново: спецификация виртуальной машины Java® никогда не определяла точно, какой метод выбирается и как следует разрешать «равенства». До Java SE 8 это в основном было не наблюдаемым различием. Однако начиная с Java SE 8, набор методов интерфейса более неоднороден, поэтому необходимо соблюдать осторожность, чтобы избежать проблем с неопределённым поведением. Следовательно:

  • Методы суперинтерфейсов, которые являются private и static, игнорируются разрешением. Это соответствует языку программирования Java, где такие методы интерфейса не наследуются.

  • Любое поведение, управляемое разрешенным методом, не должно зависеть от того, является ли метод abstract или нет.

Обратите внимание, что если результатом разрешения является метод abstract, то ссылаемый класс C может быть не-abstract. Требование, чтобы C был abstract, противоречило бы неопределённому выбору методов суперинтерфейсов. Вместо этого разрешение предполагает, что класс во время выполнения вызываемого объекта имеет конкретную реализацию метода.

5.4.3.4. Разрешение метода интерфейса

Для разрешения неразрешенной символической ссылки из D на метод интерфейса в интерфейсе C сначала разрешается символическая ссылка на C, заданная ссылкой на метод интерфейса (§5.4.3.1). Поэтому любые исключения, которые могут быть выброшены в результате неудачи разрешения ссылки на интерфейс, могут быть выброшены в результате неудачи разрешения метода интерфейса. Если ссылка на C может быть успешно разрешена, могут быть выброшены исключения, связанные с разрешением самой ссылки на метод интерфейса.

При разрешении ссылки на метод интерфейса:

  1. Если C не является интерфейсом, разрешение метода интерфейса выбросит IncompatibleClassChangeError.

  2. В противном случае, если C объявляет метод с именем и описанием, заданными ссылкой на метод интерфейса, поиск метода завершается успешно.

  3. В противном случае, если класс Object объявляет метод с именем и описанием, заданными ссылкой на метод интерфейса, у которого установлен флаг ACC_PUBLIC и не установлен флаг ACC_STATIC, поиск метода завершается успешно.

  4. В противном случае, если методы суперинтерфейсов C (с максимальной специфичностью) для имени и описания, заданных ссылкой на метод, включают ровно один метод, у которого не установлен флаг ACC_ABSTRACT, то этот метод выбирается, и поиск метода завершается успешно.

  5. В противном случае, если любой суперинтерфейс C объявляет метод с именем и описанием, заданными ссылкой на метод, у которого не установлен ни флаг ACC_PRIVATE, ни флаг ACC_STATIC, один из этих методов произвольно выбирается, и поиск метода завершается успешно.

  6. В противном случае, поиск метода завершается неудачей.

Результат разрешения метода интерфейса определяется успехом или неудачей поиска метода:

  • Если поиск метода завершается неудачей, разрешение метода интерфейса выбросит NoSuchMethodError.

  • Если поиск метода завершается успешно, а ссылаемый метод недоступен (§5.4.4) для D, разрешение метода интерфейса выбросит IllegalAccessError.

  • В противном случае, пусть <E, L1> - класс или интерфейс, в котором фактически объявлен ссылаемый метод интерфейса m, и пусть L2 - определяющий загрузчик D.

    Учитывая, что возвращаемый тип m - Tr, а формальные типы параметров m - Tf1, ..., Tfn, то:

    Если Tr не является массивом, то пусть T0 будет Tr; в противном случае пусть T0 будет типом элемента (§2.4) Tr.

    Для i = 1 до n: Если Tfi не является типом массива, то пусть Ti будет Tfi; в противном случае пусть Ti будет типом элемента (§2.4) Tfi.

    Виртуальная машина Java должна наложить ограничения загрузки TiL1 = TiL2 для i = 0 до n (§5.3.4).

Положение об обеспечении доступа необходимо, потому что разрешение метода интерфейса может выбрать метод private интерфейса C. (До Java SE 8 результатом разрешения метода интерфейса мог быть неметод public класса Object или метод static класса Object; такие результаты не соответствовали модели наследования языка программирования Java, и в Java SE 8 и выше они запрещены.)

5.4.3.5. Разрешение типа метода и дескриптора метода

Для разрешения неразрешенной символической ссылки на тип метода происходит, как если бы разрешение происходило для неразрешенных символических ссылок на классы и интерфейсы (§5.4.3.1), имена которых соответствуют типам, указанным в дескрипторе метода (§4.3.3).

Любое исключение, которое может быть выброшено в результате неудачи разрешения ссылки на класс, может быть выброшено в результате неудачи разрешения типа метода.

Результатом успешного разрешения типа метода является reference к экземпляру java.lang.invoke.MethodType, представляющему дескриптор метода.

Разрешение типа метода происходит независимо от того, содержит ли постоянный пул времени выполнения фактические символические ссылки на классы и интерфейсы, указанные в дескрипторе метода. Кроме того, разрешение считается происходящим для неразрешенных символических ссылок, поэтому неудача разрешения одного типа метода не обязательно приведет к последующей неудаче разрешения другого типа метода с тем же текстовым дескриптором метода, если соответствующие классы и интерфейсы могут быть загружены к тому времени.

Разрешение неразрешенной символической ссылки на дескриптор метода более сложно. Каждый дескриптор метода, разрешаемый виртуальной машиной Java, имеет эквивалентную последовательность инструкций, называемую его поведением байткода, указанную типом дескриптора метода. Целочисленные значения и описания девяти типов дескрипторов метода приведены в таблице 5.4.3.5-A.

Символические ссылки последовательностью инструкций на поля или методы обозначаются C.x:T, где x и T — имя и дескриптор (§4.3.2, §4.3.3) поля или метода, а C — класс или интерфейс, в котором находится поле или метод.

Таблица 5.4.3.5-A. Поведение байткода для дескрипторов методов

Тип Описание Интерпретация
1 REF_getField getfield C.f:T
2 REF_getStatic getstatic C.f:T
3 REF_putField putfield C.f:T
4 REF_putStatic putstatic C.f:T
5 REF_invokeVirtual invokevirtual C.m:(A*)T
6 REF_invokeStatic invokestatic C.m:(A*)T
7 REF_invokeSpecial invokespecial C.m:(A*)T
8 REF_newInvokeSpecial new C; dup; invokespecial C.<init>:(A*)V
9 REF_invokeInterface invokeinterface C.m:(A*)T

Пусть MH — символическая ссылка на дескриптор метода (§5.1), который разрешается. Тогда:

  • Пусть R — символическая ссылка на поле или метод, содержащаяся в MH.

    (R получено из CONSTANT_Fieldref, CONSTANT_Methodref или CONSTANT_InterfaceMethodref структуры, на которую ссылается элемент reference_index из CONSTANT_MethodHandle, из которого получен MH.)

  • Пусть T — тип поля, на который ссылается R, или тип возвращаемого значения метода, на который ссылается R. Пусть A* — последовательность (возможно пустая) типов параметров метода, на который ссылается R.

    (T и A* получены из структуры CONSTANT_NameAndType, на которую ссылается элемент name_and_type_index в структуре CONSTANT_Fieldref, CONSTANT_Methodref или CONSTANT_InterfaceMethodref, из которой получена R.)

Для разрешения MH все символические ссылки на классы, интерфейсы, поля и методы в поведении байткода MH разрешаются, используя следующие три шага:

  • Сначала разрешается R.

  • Затем происходит разрешение, как если бы разрешались неразрешенные символические ссылки на классы и интерфейсы, имена которых соответствуют каждому типу в A*, и типу T, в указанном порядке.

  • В-третьих, ссылка на экземпляр java.lang.invoke.MethodType получается так же, как при разрешении неразрешенной символической ссылки на тип метода, который содержит дескриптор метода, указанный в таблице 5.4.3.5-B для типа MH.

    Это происходит так, как если бы символическая ссылка на дескриптор метода содержала символическую ссылку на тип метода, которым в конечном итоге будет обладать разрешенный дескриптор метода. Подробная структура типа метода получается путем проверки таблицы 5.4.3.5-B.

На каждом шаге любое исключение, которое может быть выброшено в результате неудачи разрешения ссылки на класс или интерфейс или поле или метод, может быть выброшено в результате неудачи разрешения дескриптора метода.

Целью является то, что разрешение дескриптора метода может выполняться точно в тех же обстоятельствах, что и успешное разрешение виртуальной машиной Java символических ссылок в коде байткода. В частности, дескрипторы методов для private и protected членов могут быть созданы в тех классах, для которых соответствующие обычные обращения являются законными.

Таблица 5.4.3.5-B. Дескрипторы методов для дескрипторов методов

Тип Описание Дескриптор метода
1 REF_getField (C)T
2 REF_getStatic ()T
3 REF_putField (C,T)V
4 REF_putStatic (T)V
5 REF_invokeVirtual (C,A*)T
6 REF_invokeStatic (A*)T
7 REF_invokeSpecial (C,A*)T
8 REF_newInvokeSpecial (A*)C
9 REF_invokeInterface (C,A*)T

Результатом успешного разрешения дескриптора метода является reference к экземпляру java.lang.invoke.MethodHandle, который представляет дескриптор метода MH.

Дескриптор типа этого экземпляра java.lang.invoke.MethodHandle — это экземпляр java.lang.invoke.MethodType, полученный на третьем шаге разрешения дескриптора метода выше.

Дескриптор типа дескриптора метода такой, что допустимый вызов invokeExact в java.lang.invoke.MethodHandle для дескриптора метода имеет точно такие же эффекты стека, как и поведение байткода. Вызов этого дескриптора метода с допустимым набором аргументов имеет точно такой же эффект и возвращает тот же результат (если таковой имеется), что и соответствующее поведение байткода.

Если метод, на который ссылается R, имеет флаг ACC_VARARGS (см. §4.6), то экземпляр java.lang.invoke.MethodHandle — это дескриптор метода с переменным числом аргументов; в противном случае это дескриптор метода с фиксированным числом аргументов.

Дескриптор метода с переменным числом аргументов выполняет упаковку списка аргументов (JLS §15.12.4.2) при вызове через invoke, в то время как его поведение относительно invokeExact такое же, как если бы флаг ACC_VARARGS не был установлен.

Разрешение дескриптора метода выбрасывает IncompatibleClassChangeError, если метод, на который ссылается R, имеет флаг ACC_VARARGS, и либо A* является пустой последовательностью, либо последний тип параметра в A* не является типом массива. То есть создание дескриптора метода с переменным числом аргументов не выполняется.

Реализация виртуальной машины Java не обязана интернировать типы методов или дескрипторы методов. То есть две разные символические ссылки на типы методов или дескрипторы методов, которые структурно идентичны, могут не разрешаться в один и тот же экземпляр java.lang.invoke.MethodType или java.lang.invoke.MethodHandle соответственно.

Класс java.lang.invoke.MethodHandles в API платформы Java SE позволяет создавать дескрипторы методов без поведения байткода. Их поведение определяется методом java.lang.invoke.MethodHandles, который их создает. Например, дескриптор метода может, при вызове, сначала применять преобразования к своим значениям аргументов, затем передавать преобразованные значения в вызов другого дескриптора метода, затем применять преобразование к значению, возвращаемому этим вызовом, затем возвращать преобразованное значение как свой собственный результат.

5.4.3.6. Разрешение спецификатора места вызова

Для разрешения неразрешенной символической ссылки на спецификатор места вызова требуется три шага:

  • Спецификатор места вызова предоставляет символическую ссылку на обработчик метода, который должен служить методом загрузки для динамического места вызова (§4.7.23). Обработчик метода разрешается для получения reference к экземпляру java.lang.invoke.MethodHandle (§5.4.3.5).

  • Спецификатор места вызова предоставляет описатель метода, TD. reference к экземпляру java.lang.invoke.MethodType получается так, как если бы происходило разрешение символической ссылки на тип метода с теми же параметрами и возвращаемым типом, что и TD (§5.4.3.5).

  • Спецификатор места вызова предоставляет ноль или более статических аргументов, которые передают метаданные, специфичные для приложения, методу загрузки. Любые статические аргументы, которые являются символическими ссылками на классы, обработчики методов или типы методов, разрешаются так, как если бы вызывалась инструкция ldc (§ldc), чтобы получить reference к Class объектам, java.lang.invoke.MethodHandle объектам и java.lang.invoke.MethodType объектам соответственно. Любые статические аргументы, которые являются строковыми литералами, используются для получения reference к String объектам.

Результатом разрешения спецификатора места вызова является кортеж, состоящий из:

  • reference к экземпляру java.lang.invoke.MethodHandle,

  • reference к экземпляру java.lang.invoke.MethodType,

  • reference к экземплярам Class, java.lang.invoke.MethodHandle, java.lang.invoke.MethodType и String.

Во время разрешения символической ссылки на обработчик метода в спецификаторе места вызова, или разрешения символической ссылки на тип метода для описателя метода в спецификаторе места вызова, или разрешения символической ссылки на любой статический аргумент, могут быть выброшены любые исключения, относящиеся к разрешению типа метода или обработчика метода (§5.4.3.5).

5.4.4. Контроль доступа

Класс или интерфейс C является доступным для класса или интерфейса D, если и только если истинно одно из следующих утверждений:

  • C является public.

  • C и D являются членами одной и той же исполняющей среды (§5.3).

Поле или метод R является доступным для класса или интерфейса D, если и только если истинно одно из следующих утверждений:

  • R является public.

  • R является protected и объявлен в классе C, и D является либо подклассом C, либо самим C. Кроме того, если R не является static, то символическая ссылка на R должна содержать символическую ссылку на класс T, такой что T является либо подклассом D, либо суперклассом D, либо самим D.

  • R является либо protected, или имеет доступ по умолчанию (то есть ни public, ни protected, ни private), и объявлен классом в той же исполняющей среде, что и D.

  • R является private и объявлен в D.

В данном обсуждении контроля доступа опущено связанное ограничение на целевой объект поля доступа protected или вызова метода (цель должна быть класса D или подтипа D). Это требование проверяется во время процесса проверки (§4.10.1.8); оно не является частью контроля доступа на этапе связывания.

5.4.5. Переопределение

Метод экземпляра mC, объявленный в классе C, переопределяет другой метод экземпляра mA, объявленный в классе A, если либо mC совпадает с mA, либо все следующие утверждения являются истинными:

  • C является подклассом A.

  • mC имеет то же имя и описатель, что и mA.

  • mC не помечен как ACC_PRIVATE.

  • Выполняется одно из следующих утверждений:

    • mA помечен как ACC_PUBLIC; или помечен как ACC_PROTECTED; или не помечен ни как ACC_PUBLIC, ни как ACC_PROTECTED, ни как ACC_PRIVATE, и A принадлежит к той же исполняющей среде, что и C.

    • mC переопределяет метод m' (m' отличается от mC и mA), такой что m' переопределяет mA.

5.5. Инициализация

Инициализация класса или интерфейса заключается в выполнении его метода инициализации класса или интерфейса (§2.9).

Класс или интерфейс C может быть инициализирован только в результате:

  • Выполнения любой из инструкций виртуальной машины Java new, getstatic, putstatic или invokestatic, которые ссылаются на C (§new, §getstatic, §putstatic, §invokestatic). Эти инструкции ссылаются на класс или интерфейс напрямую или косвенно через ссылку на поле или метод.

    При выполнении инструкции new указанный класс инициализируется, если он ещё не был инициализирован.

    При выполнении инструкций getstatic, putstatic или invokestatic класс или интерфейс, объявляющий разрешённое поле или метод, инициализируется, если он ещё не был инициализирован.

  • Первый вызов экземпляра java.lang.invoke.MethodHandle, который был результатом разрешения метода для дескриптора метода типа 2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic) или 8 (REF_newInvokeSpecial).

    Это подразумевает, что класс метода запуска инициализируется при вызове метода запуска для инструкции invokedynamic (§invokedynamic), как часть продолжающегося разрешения спецификатора места вызова.

  • Вызов определённых рефлексивных методов в библиотеке классов (§2.12), например, в классе Class или в пакете java.lang.reflect.

  • Если C — класс, инициализация одного из его подклассов.

  • Если C — интерфейс, который объявляет метод, не являющийся abstract, не являющийся static, инициализация класса, который реализует C напрямую или косвенно.

  • Если C — класс, его назначение в качестве начального класса при запуске виртуальной машины Java (§5.2).

Перед инициализацией класс или интерфейс должен быть связан, то есть проверен, подготовлен и, по желанию, разрешён.

Поскольку виртуальная машина Java многопоточная, инициализация класса или интерфейса требует тщательной синхронизации, так как другой поток может пытаться инициализировать тот же класс или интерфейс в то же время. Существует также возможность, что инициализация класса или интерфейса может быть запрошена рекурсивно как часть инициализации этого класса или интерфейса. Реализация виртуальной машины Java отвечает за обработку синхронизации и рекурсивной инициализации, используя следующую процедуру. Она предполагает, что объект Class уже проверен и подготовлен, а объект Class содержит состояние, указывающее одну из четырёх ситуаций:

  • Данный объект Class проверен и подготовлен, но не инициализирован.

  • Этот объект Class инициализируется каким-то определённым потоком.

  • Этот объект Class полностью инициализирован и готов к использованию.

  • Этот объект Class находится в состоянии ошибки, возможно, потому что инициализация была предпринята и не удалась.

Для каждого класса или интерфейса C существует уникальная блокировка инициализации LC. Сопоставление C с LC предоставляется на усмотрение реализации виртуальной машины Java. Например, LC может быть объектом Class для C или монитором, связанным с этим объектом Class. Процедура инициализации C затем выглядит следующим образом:

  1. Синхронизироваться на блокировке инициализации, LC, для C. Это включает ожидание, пока текущий поток сможет получить LC.

  2. Если объект Class для C указывает, что инициализация C выполняется другим потоком, то освободить LC и заблокировать текущий поток до получения уведомления о завершении текущей инициализации, после чего повторить эту процедуру.

    Статус прерывания потока не затрагивается выполнением процедуры инициализации.

  3. Если объект Class для C указывает, что инициализация C выполняется текущим потоком, то это должна быть рекурсивная инициализация. Освободить LC и завершить обычно.

  4. Если объект Class для C указывает, что C уже был инициализирован, то дальнейших действий не требуется. Освободить LC и завершить обычно.

  5. Если объект Class для C находится в ошибочном состоянии, то инициализация невозможна. Освободить LC и бросить исключение NoClassDefFoundError.

  6. В противном случае, отметить, что инициализация объекта Class для C выполняется текущим потоком и освободить LC.

    Затем инициализировать каждое поле final static объекта C с постоянным значением в его атрибуте ConstantValue (§4.7.2), в порядке появления полей в структуре ClassFile.

  7. Далее, если C является классом, а не интерфейсом, и его суперкласс еще не был инициализирован, то пусть SC будет его суперклассом, а SI1, ..., SIn — все суперинтерфейсы C (прямые или косвенные), объявляющие как минимум один не-abstract, не-static метод. Порядок суперинтерфейсов задаётся рекурсивным перечислением иерархии суперинтерфейсов каждого интерфейса, прямо реализованного C. Для каждого интерфейса I, прямо реализованного C (в порядке массива interfaces C), перечисление повторяется для суперинтерфейсов I (в порядке массива interfaces I) перед возвращением I.

    Для каждого S в списке [ SC, SI1, ..., SIn ] рекурсивно выполнить всю эту процедуру для S. При необходимости сначала проверить и подготовить S.

    Если инициализация S завершается аварийно из-за брошенного исключения, то получить LC, пометить объект Class для C как ошибочный, уведомить все ожидающие потоки, освободить LC и завершить аварийно, сбросив то же исключение, которое возникло при инициализации SC.

  8. Далее, определить, включены ли утверждения для C путём запроса у определяющего загрузчика классов.

  9. Далее, выполнить метод инициализации класса или интерфейса для C.

  10. Если выполнение метода инициализации класса или интерфейса завершилось нормально, то получить LC, пометить объект Class для C как полностью инициализированный, уведомить все ожидающие потоки, освободить LC и завершить эту процедуру нормально.

  11. В противном случае, метод инициализации класса или интерфейса должен был завершиться аварийно, сбросив какое-либо исключение E. Если класс E не является Error или его подклассом, то создать новый экземпляр класса ExceptionInInitializerError со E в качестве аргумента и использовать этот объект вместо E на следующем шаге. Если новый экземпляр ExceptionInInitializerError не может быть создан из-за возникновения OutOfMemoryError, то использовать объект OutOfMemoryError вместо E на следующем шаге.

  12. Получить LC, пометить объект Class для C как ошибочный, уведомить все ожидающие потоки, освободить LC и завершить эту процедуру аварийно с причиной E или её заменой, как определено на предыдущем шаге.

Реализация Java Virtual Machine может оптимизировать эту процедуру, опуская получение блокировки на шаге 1 (и освобождение на шаге 4/5), когда она может определить, что инициализация класса уже завершена, при условии, что в терминах модели памяти Java все упорядочения happens-before (JLS §17.4.5), которые существовали бы, если бы блокировка была получена, всё ещё существуют, когда выполняется оптимизация.

5.6. Связывание реализаций методов нативных методов

Связывание — это процесс, посредством которого функция, написанная на языке, отличном от Java, и реализующая метод native, интегрируется в Java Virtual Machine, чтобы её можно было выполнить. Хотя этот процесс традиционно называется линковкой, в спецификации используется термин связывание, чтобы избежать путаницы со связыванием классов или интерфейсов Java Virtual Machine.

5.7. Выход из Java Virtual Machine

Java Virtual Machine завершается, когда какой-то поток вызывает метод exit класса Runtime или класса System, или метод halt класса Runtime, и операция exit или halt разрешена менеджером безопасности.

Кроме того, спецификация JNI (Java Native Interface) описывает завершение Java Virtual Machine при использовании API вызовов JNI для загрузки и выгрузки Java Virtual Machine.

© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API