Spec-Zone.ru › Java Virtual Machine Specification 11

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

Содержание

5.1. Пул констант во время выполнения
5.2. Запуск виртуальной машины Java
5.3. Создание и загрузка
5.3.1. Загрузка с использованием загрузчика Bootstrap
5.3.2. Загрузка с использованием пользовательского загрузчика классов
5.3.3. Создание классов массивов
5.3.4. Ограничения загрузки
5.3.5. Получение класса из представления файла class
5.3.6. Модули и слои
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.4.6. Выбор метода
5.5. Инициализация
5.6. Связывание реализаций методов нативных
5.7. Выход виртуальной машины Java

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

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

END_OF_DOCUMENT_MARKER

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

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

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

Символические ссылки в постоянном пуле времени выполнения получены из элементов таблицы constant_pool в соответствии со структурой каждого элемента:

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

    • Для класса или интерфейса, не являющегося массивом, имя — это двоичное имя (§4.2.1) класса или интерфейса.

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

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

      • В противном случае, если тип элемента — ссылка на тип, он представляется символом L ASCII, за которым следует двоичное имя типа элемента, за которым следует символ ; 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_Dynamic_info (§4.4.10). Такая ссылка содержит:

    • символическую ссылку на обработчик метода, который будет вызван для вычисления значения константы;

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

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

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

    • символическую ссылку на обработчик метода, который будет вызван в процессе инструкции invokedynamic (§invokedynamic) для вычисления экземпляра java.lang.invoke.CallSite;

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

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

Статические константы в постоянном пуле времени выполнения также получены из элементов таблицы constant_pool в соответствии со структурой каждого элемента:

  • Строковая константа — это reference экземпляра класса String и получена из структуры CONSTANT_String_info (§4.4.3). Для получения строковой константы виртуальная машина Java проверяет последовательность кодовых точек, заданную структурой CONSTANT_String_info:

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

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

  • Числовые константы получены из структур 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 с двойной точностью. Числовые константы, полученные из этих структур, должны быть значениями, которые могут быть представлены в форматах IEEE 754 с одинарной и двойной точностью соответственно.

Остальные структуры в таблице constant_pool — описательные структуры CONSTANT_NameAndType_info, CONSTANT_Module_info и CONSTANT_Package_info, а также основная структура CONSTANT_Utf8_info — используются только косвенно при построении постоянного пула времени выполнения. Ни один элемент в постоянном пуле времени выполнения не соответствует напрямую этим структурам.

Некоторые элементы постоянного пула времени выполнения являются загружаемыми, что означает:

  • Они могут быть помещены в стек инструкциями семейства ldc (§ldc, §ldc_w, §ldc2_w).

  • Они могут быть статическими аргументами для методов загрузки для динамически вычисляемых констант и сайтов вызовов (§5.4.3.6).

Элемент в постоянном пуле времени выполнения загружаемый, если он получен из загружаемого элемента таблицы constant_pool (см. Таблица 4.4-C). Соответственно, следующие элементы постоянного пула времени выполнения являются загружаемыми:

  • Символические ссылки на классы и интерфейсы

  • Символические ссылки на обработчики методов

  • Символические ссылки на типы методов

  • Символические ссылки на динамически вычисляемые константы

  • Статические константы

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

Java виртуальная машина запускается, создавая начальный класс или интерфейс с помощью загрузчика начальных классов (§5.3.1) или пользовательского загрузчика классов (§5.3.2). Затем Java виртуальная машина связывает начальный класс или интерфейс, инициализирует его и вызывает метод public static метод 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 может быть либо загрузчиком базовых классов, либо пользовательским загрузчиком классов.

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

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

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

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

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

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

    Если тип компонента — тип reference, доступность массива классов определяется доступностью его типа компонента (§5.4.4). В противном случае к массиву классов имеют доступ все классы и интерфейсы.

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 для класса или интерфейса nonarray 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 или экземпляр одного из его подклассов.

      Это происходит, когда предполагаемое представление имеет элемент this_class, который указывает имя, отличное от N, или элемент access_flags, у которого установлен флаг ACC_MODULE.

  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.3.6. Модули и слои

Виртуальная машина Java поддерживает организацию классов и интерфейсов в модули. Принадлежность класса или интерфейса C к модулю M используется для управления доступом к C из классов и интерфейсов в модулях, отличных от M (§5.4.4).

Принадлежность модулю определяется в терминах пакетов времени выполнения (§5.3). Программа определяет имена пакетов в каждом модуле и загрузчики классов, которые будут создавать классы и интерфейсы указанных пакетов; затем она указывает пакеты и загрузчики классов вызову метода defineModules класса ModuleLayer. Вызов defineModules заставляет виртуальную машину Java создавать новые модули времени выполнения, связанные с пакетами времени выполнения загрузчиков классов.

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

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

Класс, созданный загрузчиком классов, находится ровно в одном пакете времени выполнения и, следовательно, ровно в одном модуле времени выполнения, потому что виртуальная машина Java не поддерживает ассоциацию пакета времени выполнения с (или, более образно, "разделение по") несколькими модулями времени выполнения.

Модуль времени выполнения неявным образом связан ровно с одним загрузчиком классов в силу семантики defineModules. С другой стороны, загрузчик классов может создавать классы в более чем одном модуле времени выполнения, поскольку виртуальная машина Java не требует, чтобы все пакеты времени выполнения загрузчика классов были связаны с одним модулем времени выполнения.

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

Каждый модуль времени выполнения, созданный defineModules, является частью слоя. Слой представляет собой набор загрузчиков классов, которые совместно служат для создания классов в наборе модулей времени выполнения. Существуют два типа слоев: слой загрузки, поставляемый виртуальной машиной Java, и пользовательские слои. Слой загрузки создается при запуске виртуальной машины Java зависящим от реализации способом. Он связывает стандартный модуль времени выполнения java.base со стандартными пакетами времени выполнения, определенными загрузчиком базовых классов, такими как java.lang. Пользовательские слои создаются программами для построения наборов модулей времени выполнения, которые зависят от java.base и других стандартных модулей времени выполнения.

Модуль времени выполнения неявным образом является частью ровно одного слоя в силу семантики defineModules. Однако загрузчик классов может создавать классы в модулях времени выполнения разных слоев, поскольку один и тот же загрузчик классов может быть указан для нескольких вызовов defineModules. Управление доступом определяется модулем времени выполнения класса, а не загрузчиком класса, который создал класс, или слоем(ами), обслуживаемым загрузчиком.

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

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

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

Возможно, что загрузчик классов определит класс или интерфейс в пакете времени выполнения, который не был связан с модулем времени выполнения ни одним из слоев, обслуживаемых загрузчиком. Это может произойти, если пакет времени выполнения воплощает имя пакета, которое не было указано для defineModules, или если класс или интерфейс имеет простое двоичное имя (§4.2.1) и, следовательно, является членом пакета времени выполнения, который воплощает безымянный пакет (JLS §7.4.2). В любом случае класс или интерфейс обрабатываются как член специального модуля времени выполнения, который неявно связан с загрузчиком классов. Этот специальный модуль времени выполнения известен как безымянный модуль загрузчика классов. Пакет времени выполнения класса или интерфейса связан с безымянным модулем загрузчика классов. Для безымянных модулей существуют специальные правила, призванные максимизировать их взаимодействие с другими модулями времени выполнения, как показано ниже:

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

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

  • Каждый безымянный модуль читает каждый модуль времени выполнения.

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

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

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

Этот стандарт предоставляет реализации гибкость в отношении времени выполнения связующих действий (и, из-за рекурсии, загрузки), при условии соблюдения всех следующих свойств:

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

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

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

  • Символическая ссылка на динамически вычисляемую константу не разрешается до тех пор, пока не будет выполнен (i) инструкция ldc, ldc_w или ldc2_w, которая ссылается на неё, или (ii) вызов метода Bootstrap, который ссылается на неё как на статический аргумент.

    Символическая ссылка на динамически вычисляемую точку вызова не разрешается до тех пор, пока не будет выполнен вызов метода Bootstrap, который ссылается на неё как на статический аргумент.

Например, реализация 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):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Разрешение неразрешенной символической ссылки на (i) класс или интерфейс, (ii) поле, (iii) метод, (iv) тип метода, (v) обработчик метода или (vi) динамически вычисляемую константу выполняется в соответствии с правилами, приведенными в §5.4.3.1 до §5.4.3.5. В первых трех из этих разделов класс или интерфейс, в постоянном пуле которого появляется символическая ссылка, помечается как D. Затем:

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

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

  • Если во время разрешения символической ссылки возникает ошибка, то она является (i) экземпляром IncompatibleClassChangeError (или подклассом); (ii) экземпляром Error (или подклассом), возникшим при разрешении или вызове метода-оболочки; или (iii) экземпляром LinkageError (или подклассом), возникшим из-за сбоя загрузки класса или нарушения ограничений загрузчика. Ошибка должна быть сгенерирована в точке программы, которая (прямо или косвенно) использует символическую ссылку.

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

Поскольку ошибки, возникшие при первой попытке разрешения, выбрасываются снова при последующих попытках, класс в одном модуле, который пытается получить доступ через разрешение символической ссылки в своем постоянном пуле к неэкспортированному типу public в другом модуле, всегда получит ту же ошибку, указывающую на недоступный тип (§5.4.4), даже если API Java SE Platform используется для динамического экспорта пакета типа public в какой-то момент после первой попытки класса.

Разрешение неразрешенной символической ссылки на динамически вычисляемую точку вызова выполняется в соответствии с правилами, приведенными в §5.4.3.6. Затем:

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

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

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

  • Если при разрешении символической ссылки возникает ошибка, то она является (i) экземпляром IncompatibleClassChangeError (или подклассом); (ii) экземпляром Error (или подклассом), возникшим при разрешении или вызове метода-оболочки; или (iii) экземпляром LinkageError (или подклассом), возникшим из-за сбоя загрузки класса или нарушения ограничений загрузчика. Ошибка должна быть сгенерирована в точке программы, которая (прямо или косвенно) использует символическую ссылку.

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

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

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

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

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

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

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

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

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

  3. Наконец, применяется контроль доступа для доступа из D к C (§5.4.4).

Если шаги 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.

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

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

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

      Пусть <E, L1> - это класс или интерфейс, в котором фактически объявлено указанное поле. Пусть L2 является определяющим загрузчиком D. Учитывая, что тип указанного поля - Tf: если Tf не является типом массива, пусть T будет Tf; в противном случае пусть T будет типом элемента Tf.

      Виртуальная машина Java накладывает ограничение загрузки, что 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.3), то поиск метода успешен. Все имена классов, упомянутые в описателе, разрешаются (§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.

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

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

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

      Пусть <E, L1> — класс или интерфейс, в котором фактически объявлен метод m. Пусть L2 — определяющий загрузчик D. Учитывая, что тип возвращаемого значения m — Tr, а типы формальных параметров m — Tf1, ..., Tfn:

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

      Для i = 1 до n: Если Tfi не является массивом, пусть Ti будет Tfi; в противном случае, пусть Ti будет типом элемента 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. В противном случае, если методы максимально-специфических суперинтерфейсов (§5.4.3.3) C для имени и описания, указанных ссылкой на метод, включают ровно один метод, у которого не установлен его флаг ACC_ABSTRACT, этот метод выбирается, и поиск метода завершается успешно.

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

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

Результат разрешения метода интерфейса определяется следующим образом:

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

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

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

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

      Пусть <E, L1> - класс или интерфейс, в котором фактически объявлен сославшийся на метод интерфейса m. Пусть L2 - определяющий загрузчик D. Учитывая, что тип возвращаемого значения m - Tr, а типы формальных параметров m - Tf1, ..., Tfn:

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

      Для i = 1 до n: Если Tfi не является типом массива, пусть Ti будет Tfi; в противном случае, пусть Ti будет типом элемента 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.

    Например, R — это символическая ссылка на C . f для поведения байткода типа 1 и символическая ссылка на C . <init> для поведения байткода типа 8.

    Если поведение байткода MH — тип 7 (REF_invokeSpecial), то C должен быть текущим классом или интерфейсом, суперклассом текущего класса, прямым суперинтерфейсом текущего класса или интерфейса или Object.

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

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

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

  1. R разрешается. Это происходит так же, как при разрешении поля (§5.4.3.2), когда поведение байткода MH — 1, 2, 3 или 4, и как при разрешении метода (§5.4.3.3), когда поведение байткода MH — 5, 6, 7 или 8, и как при разрешении метода интерфейса (§5.4.3.4), когда поведение байткода MH — 9.

  2. Следующие ограничения применяются к результату разрешения R. Эти ограничения соответствуют ограничениям, которые будут применяться во время проверки или выполнения последовательности инструкций для соответствующего поведения байткода.

    • Если поведение байткода MH — 8 (REF_newInvokeSpecial), то R должно разрешаться на метод инициализации экземпляра, объявленный в классе C.

    • Если R разрешается на член protected, то применяются следующие правила в зависимости от типа поведения байткода MH:

      • Для типов 1, 3 и 5 (REF_getField, REF_putField и REF_invokeVirtual): Если C.f или C.m разрешились на поле или метод класса protected, и C находится в другом пакете времени выполнения, чем текущий класс, то C должен быть совместим с текущим классом.

      • Для типа 8 (REF_newInvokeSpecial): Если C . <init> разрешился на метод protected, то C должен быть объявлен в том же пакете времени выполнения, что и текущий класс.

    • R должно разрешиться на член static или не-static член в зависимости от типа поведения байткода MH:

      • Для типов 1, 3, 5, 7 и 9 (REF_getField, REF_putField, REF_invokeVirtual, REF_invokeSpecial и REF_invokeInterface): C.f или C.m должны разрешиться на поле или метод, не являющееся static.

      • Для типов 2, 4 и 6 (REF_getStatic, REF_putStatic и REF_invokeStatic): C.f или C.m должны разрешиться на поле или метод класса static.

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

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

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

    Таблица 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

На шагах 1, 3 и 4 любые исключения, которые могут быть вызваны в результате неудачи разрешения символической ссылки на класс, интерфейс, поле или метод, могут быть вызваны в результате неудачи разрешения обработчика метода. На шаге 2 любая ошибка из-за указанных ограничений приводит к сбою разрешения обработчика метода из-за IllegalAccessError.

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

Результатом успешного разрешения обработчика метода является 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 Virtual Machine не обязана интернировать типы методов или обработчики методов. То есть, две разные символические ссылки на типы методов или обработчики методов, которые структурно идентичны, могут не разрешаться в один и тот же экземпляр java.lang.invoke.MethodType или java.lang.invoke.MethodHandle соответственно.

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

5.4.3.6. Разрешение динамически вычисляемых констант и сайтов вызова

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

Первая задача включает следующие шаги:

  1. R предоставляет символическую ссылку на обработчик метода инициализации. Обработчик метода инициализации разрешается (§5.4.3.5) для получения reference экземпляра java.lang.invoke.MethodHandle.

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

    Если R — это символическая ссылка на динамически вычисляемую константу, то пусть D будет описателем типа обработчика метода инициализации. (То есть, D — это reference экземпляра java.lang.invoke.MethodType.) Первый тип параметра, указанный в D, должен быть java.lang.invoke.MethodHandles.Lookup, в противном случае разрешение завершается ошибкой с BootstrapMethodError. По историческим причинам, обработчик метода инициализации для динамически вычисляемого сайта вызова не ограничен аналогично.

  2. Если R — это символическая ссылка на динамически вычисляемую константу, то она предоставляет описание поля.

    Если описание поля указывает на примитивный тип, то получается reference предварительно определенного Class объекта, представляющего этот тип (см. метод isPrimitive в классе Class).

    В противном случае описание поля указывает на тип класса или интерфейса или тип массива. Получается reference объекта Class, представляющего тип, указанный в описании поля, как если бы это было разрешение неразрешенной символической ссылки на класс или интерфейс (§5.4.3.1), имя которого соответствует типу, указанному в описании поля.

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

  3. Если R — это символическая ссылка на динамически вычисляемый сайт вызова, то она предоставляет описание метода.

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

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

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

    • Если A — строковая константа, то получается reference её экземпляра класса String.

    • Если A — числовая константа, то получается reference экземпляра java.lang.invoke.MethodHandle следующим образом:

      1. Пусть v — значение числовой константы, а T — описание поля, соответствующее типу числовой константы.

      2. Пусть MH — обработчик метода, полученный как при вызове метода identity класса java.lang.invoke.MethodHandles с аргументом, представляющим класс Object.

      3. Получается reference экземпляра java.lang.invoke.MethodHandle, как при вызове MH.invoke(v) с описанием метода (T)Ljava/lang/Object;.

    • Если A — это символическая ссылка на динамически вычисляемую константу с описанием поля, указывающим на примитивный тип T, то A разрешается, производя примитивное значение v. Учитывая v и T, получается reference экземпляра java.lang.invoke.MethodHandle согласно процедуре, описанной выше для числовых констант.

    • Если A — любой другой вид символической ссылки, то результатом является результат разрешения A.

    Среди символических ссылок в пуле постоянных времени выполнения, символические ссылки на динамически вычисляемые константы являются специальными, поскольку они получены из constant_pool записей, которые могут синтаксически ссылаться на себя через атрибут BootstrapMethods (§4.7.23). Однако виртуальная машина Java не поддерживает разрешение символической ссылки на динамически вычисляемую константу, зависящую от себя (то есть, как статический аргумент к своему методу инициализации). Соответственно, когда и R, и A — это символические ссылки на динамически вычисляемые константы, если A совпадает с R или A предоставляет статический аргумент, который (прямо или косвенно) ссылается на R, то разрешение завершается с ошибкой StackOverflowError в точке, где потребовалось бы повторное разрешение R.

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

    Аналогичный цикл может возникнуть, если тело метода инициализации ссылается на динамически вычисляемую константу, которая в данный момент разрешается. Это всегда было возможно для инициализаций invokedynamic, и не требует специального обращения при разрешении; рекурсивные вызовы invokeWithArguments естественным образом приведут к StackOverflowError.

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

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

  1. Массив выделяется с типом компонента Object и длиной n+3, где n — количество статических аргументов, заданных R (n ≥ 0).

    Нулевой компонент массива устанавливается в reference экземпляра java.lang.invoke.MethodHandles.Lookup для класса, в котором встречается R, созданный так, как если бы выполнялся метод lookup класса java.lang.invoke.MethodHandles.

    Первый компонент массива устанавливается в reference экземпляра String, который обозначает N, полное имя, заданное R.

    Второй компонент массива устанавливается в reference экземпляра Class или java.lang.invoke.MethodType, который был получен ранее для дескриптора поля или дескриптора метода, заданного R.

    Последующие компоненты массива устанавливаются в reference, полученные ранее при разрешении статических аргументов R, если таковые имеются. reference появляются в массиве в том же порядке, что и соответствующие статические аргументы, заданные R.

    Реализация Java Virtual Machine может быть способна пропустить выделение массива и без изменения наблюдаемого поведения напрямую передать аргументы в метод запуска.

  2. Обработчик метода запуска вызывается, как если бы выполнялся вызов BMH.invokeWithArguments(args), где BMH — это обработчик метода запуска, а args — это массив, выделенный выше.

    Из-за поведения метода invokeWithArguments класса java.lang.invoke.MethodHandle, дескриптор типа обработчика метода запуска не обязательно должен точно соответствовать типам аргументов во время выполнения. Например, тип второго параметра обработчика метода запуска (соответствующий полному имени в первом компоненте массива выше) мог быть Object вместо String. Если обработчик метода запуска имеет переменную арность, то некоторые или все аргументы могут быть собраны в параметр массива.

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

    Если вызов завершается сбросом исключения, являющегося экземпляром Error или подклассом Error, разрешение завершается с этим исключением.

    Если вызов завершается сбросом исключения, которое не является экземпляром Error или подклассом Error, разрешение завершается с BootstrapMethodError, причиной которого является сброшенное исключение.

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

Третья задача, чтобы проверить reference, o, созданные вызовом обработчика метода запуска, заключается в следующем:

  • Если R — символическая ссылка на динамически вычисляемую константу, то o преобразуется в тип T, тип, указанный в дескрипторе поля, заданном R.

    Преобразование o происходит так, как если бы выполнялся вызов MH.invoke(o) с дескриптором метода (Ljava/lang/Object;)T, где MH — обработчик метода, созданный так, как если бы выполнялся вызов метода identity класса java.lang.invoke.MethodHandles с аргументом, представляющим класс Object.

    Результатом преобразования o является результат разрешения.

    Если преобразование завершается сбросом исключения NullPointerException или ClassCastException, разрешение завершается с BootstrapMethodError.

  • Если R — символическая ссылка на динамически вычисляемый сайт вызова, то o — результат разрешения, если у него есть все следующие свойства:

    • o не является null.

    • o — экземпляр java.lang.invoke.CallSite или подкласс java.lang.invoke.CallSite.

    • Тип java.lang.invoke.CallSite семантически равен дескриптору метода, заданному R.

    Если o не имеет этих свойств, разрешение завершается с BootstrapMethodError.

Многие из вышеперечисленных шагов выполняют вычисления «как если бы выполнялся вызов» определенных методов. В каждом случае поведение вызова подробно описано в спецификациях для invokestatic и invokevirtual. Вызов происходит в потоке и из класса, который пытается разрешить символическую ссылку R. Однако соответствующие ссылки на методы не должны появляться в пуле постоянных во время выполнения, не используется стек операндов какого-либо конкретного метода, и значение элемента max_stack атрибута Code любого метода не проверяется для вызова.

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

5.4.4. Управление доступом

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

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

  • C является public, и является членом той же исполняемой модули, что и D (§5.3.6).

  • C является public, и является членом другой исполняемой модули, чем D, и исполняемая модуль C читается исполняемой модулью D, и исполняемая модуль C экспортирует исполняемый пакет C в исполняемую модуль D.

  • C не является public, и C и D являются членами одного и того же исполняемого пакета.

Если C недоступен для D, управление доступом генерирует исключение IllegalAccessError. В противном случае управление доступом успешно.

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

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

  • R является protected и объявлен в классе C, и D является либо подклассом C, либо самим C.

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

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

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

  • R является private и объявлен классом или интерфейсом C, который принадлежит тому же гнезду, что и D, согласно тесту гнездования ниже.

Если R недоступен для D, то:

  • Если R является public, protected или имеет доступ по умолчанию, тогда управление доступом генерирует исключение IllegalAccessError.

  • Если R является private, то тест гнездования не прошёл, и управление доступом терпит неудачу по той же причине.

В противном случае управление доступом успешно.

Гнездо — это набор классов и интерфейсов, которые позволяют взаимно обращаться к своим private членам. Один из классов или интерфейсов является хостом гнезда. Он перечисляет классы и интерфейсы, которые принадлежат к гнезду, используя атрибут NestMembers (§4.7.29). Каждый из них, в свою очередь, обозначает его как хост гнезда, используя атрибут NestHost (§4.7.28). Класс или интерфейс, у которого отсутствует атрибут NestHost, принадлежит гнезду, которое он сам размещает; если также у него отсутствует атрибут NestMembers, это гнездо является одиночным, состоящим только из самого класса или интерфейса.

Чтобы определить, принадлежит ли класс или интерфейс C тому же гнезду, что и класс или интерфейс D, применяется тест гнездования. C и D принадлежат одному гнезду, если и только если тест гнездования проходит успешно. Тест гнездования выполняется следующим образом:

  • Если C и D — это один и тот же класс или интерфейс, то тест гнездования пройден успешно.

  • В противном случае выполняются следующие шаги в порядке их перечисления:

    1. Определяется хост гнезда D, H (ниже). Если выбрасывается исключение, то тест гнездования терпит неудачу по той же причине.

    2. Определяется хост гнезда C, H' (ниже). Если выбрасывается исключение, то тест гнездования терпит неудачу по той же причине.

    3. Сравниваются H и H'. Если H и H' — это один и тот же класс или интерфейс, то тест гнездования пройден успешно. В противном случае тест гнездования терпит неудачу, сгенерировав исключение IllegalAccessError.

Хост гнезда класса или интерфейса M определяется следующим образом:

  • Если M не имеет атрибута NestHost, то M является собственным хостом гнезда.

  • В противном случае M имеет атрибут NestHost, и его элемент host_class_index используется в качестве индекса в постоянном пуле исполняемой памяти M. Символическая ссылка по этому индексу разрешается к классу или интерфейсу H (§5.4.3.1).

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

    Если выполняется любое из следующих условий, генерируется исключение IncompatibleClassChangeError:

    • H не находится в том же исполняемом пакете, что и M.

    • H не имеет атрибута NestMembers.

    • H имеет атрибут NestMembers, но в его массиве classes нет записи, которая ссылается на класс или интерфейс с именем N, где N — это имя M.

    В противном случае H является хостом гнезда для M.

5.4.5. Переопределение метода

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

  • mC имеет то же имя и дескриптор, что и mA.

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

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

    • mA помечен как ACC_PUBLIC.

    • mA помечен как ACC_PROTECTED.

    • mA не помечен как ACC_PUBLIC, ACC_PROTECTED или ACC_PRIVATE, и либо (а) объявление mA находится в той же среде выполнения, что и объявление mC, либо (b) если mA объявлен в классе A, а mC объявлен в классе C, то существует метод mB, объявленный в классе B, такой, что C является подклассом B, а B — подклассом A, и mC может переопределить mA.

Часть (b) конечного случая позволяет "транзитивно переопределять" методы с доступом по умолчанию. Например, рассмотрим следующие объявления классов в пакете P:

public class A           {        void m() {} }
public class B extends A { public void m() {} }
public class C extends B {        void m() {} }

и следующее объявление класса в другом пакете:

public class D extends P.C { void m() {} }

Тогда:

  • B.m может переопределить A.m.

  • C.m может переопределить B.m и A.m.

  • D.m может переопределить B.m и, транзитивно, A.m, но не может переопределить C.m.

5.4.6. Выбор метода

Во время выполнения инструкции invokeinterface или invokevirtual метод выбирается относительно (i) типа объекта в стеке во время выполнения и (ii) метода, ранее разрешенного инструкцией. Правила выбора метода относительно класса или интерфейса C и метода mR следующие:

  1. Если mR помечен как ACC_PRIVATE, то это выбранный метод.

  2. В противном случае выбранный метод определяется следующим процедурой поиска:

    • Если C содержит объявление метода экземпляра m, который может переопределить mR (§5.4.5), то m является выбранным методом.

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

    • В противном случае определяются максимально-специфические методы суперинтерфейсов C (§5.4.3.3). Если ровно один совпадает с именем и дескриптором mR и не является abstract, то это выбранный метод.

      Любой максимально-специфический метод суперинтерфейса, выбранный на этом шаге, может переопределить mR; нет необходимости в явном проверке этого.

Хотя C обычно является классом, он может быть интерфейсом, когда эти правила применяются во время подготовки (§5.4.2).

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

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

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

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

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

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

  • Первый вызов метода java.lang.invoke.MethodHandle экземпляра, который был результатом разрешения метода-обработчика (§5.4.3.5) для метода-обработчика типа 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.

  • Его назначение начальным классом или интерфейсом при запуске виртуальной машины 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 — все его суперинтерфейсы (прямые или косвенные), которые объявляют хотя бы один метод, не являющийся abstract и не являющийся static. Порядок суперинтерфейсов задается рекурсивным перечислением иерархии суперинтерфейсов каждого интерфейса, непосредственно реализованного C. Для каждого интерфейса I, непосредственно реализованного C (в порядке массива interfaces C), перечисление рекурсивно выполняется для суперинтерфейсов I (в порядке массива interfaces I) перед возвратом I.

    Для каждого S в списке [ SC, SI1, ..., SIn ], если S еще не инициализирован, то рекурсивно выполнить всю эту процедуру для 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, все упорядочения «происходит-раньше» (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, когда используется JNI Invocation API для загрузки и выгрузки 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