Spec-Zone.ru › Java Virtual Machine Specification 24

Глава 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.

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).

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

Остальные структуры в таблице 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 (см. Table 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 включает построение специфичной для реализации внутренней представления C в области методов виртуальной машины Java (§2.5.4).

Создание класса или интерфейса инициируется другим классом или интерфейсом D, постоянная пул которого символично ссылается на C с помощью имени N (§5.4.3.1). Если N не обозначает класс массива, виртуальная машина Java полагается на загрузчик классов для поиска двоичного представления класса или интерфейса под названием N (§4.1). После того, как загрузчик классов найдет двоичное представление, он, в свою очередь, использует виртуальную машину Java для вывода класса или интерфейса C из двоичного представления и создания C в области методов. Классы массивов не имеют внешнего двоичного представления; они создаются виртуальной машиной Java с помощью другого процесса.

Создание класса или интерфейса также может быть инициировано вызовом D методов определённых библиотек классов платформы Java SE (§2.12), таких как рефлексия.

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

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

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

Независимо от того, загружает ли L C напрямую или косвенно, мы говорим, что L инициирует загрузку C, или, что L является загрузчиком-инициатором для C. Любые загрузчики в цепочке делегирования между L1 и L2 не считаются загрузчиками-инициаторами для C.

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

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

  • NLi - где N обозначает имя класса или интерфейса, а Li обозначает загрузчик-инициатор для класса или интерфейса.

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

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

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

  • Если N обозначает либо не-массивовый класс, либо интерфейс, и D был определен загрузчиком по умолчанию, то загрузчик по умолчанию инициирует загрузку C (§5.3.1).

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

  • Если N обозначает класс массива, то виртуальная машина Java создает класс массива C с именем N, связанный с загрузчиком-определяющим для D (§5.3.3).

    Хотя загрузчик-определяющий для D актуален при создании класса массива, он не используется для загрузки и создания класса массива.

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

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

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

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

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

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

5.3.1. Загрузка с помощью загрузчика класса Bootstrap

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

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

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

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

Если предполагаемое представление C не найдено, загрузчик класса Bootstrap выбрасывает исключение ClassNotFoundException. Процесс загрузки и создания C завершается ошибкой NoClassDefFoundError, причиной которой является ClassNotFoundException.

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

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

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

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

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

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

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

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

Следующие правила применяются независимо от выполняемой операции:

  • Если загрузчик класса не может найти предполагаемое представление класса или интерфейса, обозначенного как N, он должен сгенерировать исключение ClassNotFoundException. Процесс загрузки и создания C завершается ошибкой NoClassDefFoundError, причиной которой является ClassNotFoundException.

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

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

Если вызов loadClass для L имеет результат, то:

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

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

С 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, либо пользовательским загрузчиком класса.

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

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

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

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

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

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

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

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

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

Когда класс или интерфейс C = <N1, L1> делает символическую ссылку на поле или метод другого класса или интерфейса D = <N2, L2>, символическая ссылка включает в себя дескриптор, определяющий тип поля или возвращаемые и аргументные типы метода. Необходимо, чтобы любое имя класса или интерфейса N, указанное в дескрипторе поля или метода (§4.3.2, §4.3.3), обозначало один и тот же класс или интерфейс при загрузке загрузчиком 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, который виртуальная машина Java записала как инициатор загрузки класса C с именем N.

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

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

  • C ≠ C '.

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

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

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

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

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

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

  2. Далее виртуальная машина Java пытается разобрать предполагаемое представление. Предполагаемое представление может на самом деле не быть допустимым представлением C, поэтому получение должно обнаружить следующие проблемы:

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

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

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

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

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

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

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

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

    • В противном случае, если класс, указанный как непосредственный суперкласс C, имеет атрибут PermittedSubclasses (§4.7.31), и выполняется любое из следующих условий, получение вызовет исключение IncompatibleClassChangeError:

      • Суперкласс находится в другом модуле выполнения, чем C (§5.3.6).

      • C не имеет установленного флага ACC_PUBLIC (§4.1), и суперкласс находится в другом пакете выполнения, чем C (§5.3).

      • Ни один элемент массива classes атрибута PermittedSubclasses суперкласса не ссылается на класс или интерфейс с именем N.

    • В противном случае, если C является классом, и какой-либо метод экземпляра, объявленный в C, может переопределить (§5.4.5) метод экземпляра final, объявленный в суперклассе C, получение вызовет исключение IncompatibleClassChangeError.

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

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

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

    • В противном случае, для каждого непосредственного суперинтерфейса, указанного C, если суперинтерфейс имеет атрибут PermittedSubclasses (§4.7.31), и выполняется любое из следующих условий, получение вызовет исключение IncompatibleClassChangeError:

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

      • C не имеет установленного флага ACC_PUBLIC (§4.1), и суперинтерфейс находится в другом пакете выполнения, чем C.

      • Ни один элемент массива classes атрибута PermittedSubclasses суперинтерфейса не ссылается на класс или интерфейс с именем N.

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

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

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

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

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

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. Связывание

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

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

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

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

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

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

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

Например, реализация 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 = <N2, L2>, для каждого имени класса или интерфейса N, указанного в дескрипторе m (§4.3.3), Java Virtual Machine накладывает ограничение на загрузку NL1 = NL2.

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

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

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 используется для динамической экспозиции пакета типа 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.

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

  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.

      Для любого имени класса или интерфейса N, упомянутого в описателе указанного поля (§4.3.2), виртуальная машина Java накладывает ограничение загрузки NL1 = NL2 (§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.5).

      Разрешенный метод — это объявление метода с полиморфной сигнатурой. Необязательно, чтобы 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.

      Для каждого имени класса или интерфейса N, упомянутого в дескрипторе искомого метода (§4.3.3), виртуальная машина Java накладывает ограничение загрузки NL1 = NL2 (§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.

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

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

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

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

      Для каждого имени класса или интерфейса N, упомянутого в описателе ссылаемого метода (§4.3.3), виртуальная машина Java накладывает ограничение на загрузку NL1 = NL2 (§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 — символическая ссылка на C . f для поведения байткода типа 1 и символическая ссылка на C . <init> для поведения байткода типа 8.

  • Пусть C — класс, интерфейс или тип массива, на который ссылается R.

  • Пусть T — тип, заданный дескриптором поля R, или тип возвращаемого значения, заданный дескриптором метода R. Пусть A* — последовательность (возможно, пустая) типов параметров, заданная дескриптором метода 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 типа 7 (REF_invokeSpecial), то C должен быть текущим классом или интерфейсом, суперклассом текущего класса, прямым суперинтерфейсом текущего класса или интерфейса или Object.

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

Целью является то, что разрешение обработчика метода может выполняться в тех же самых обстоятельствах, при которых виртуальная машина Java успешно проверяет и разрешает символические ссылки в поведении байткода. В частности, обработчики методов для 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 не обязана интернировать типы методов или обработчики методов. То есть две разные символические ссылки на типы методов или обработчики методов, которые структурно идентичны, могут не разрешаться в один и тот же экземпляр 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 на объект, представляющий число, по следующей процедуре:

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

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

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

    • Если A — символическая ссылка на динамически вычисленную константу с описанием поля, указывающим на примитивный тип T, то A разрешается, порождая примитивное значение v. Учитывая v и T, получается reference на объект, кодирующий v в соответствии с процедурой, описанной выше для числовых констант.

    • Если 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 любого метода не налагается на вызов.

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

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

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

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

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

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

    1. Пусть H — хост гнезда D, если хост гнезда D был ранее определен. Если хост гнезда D не был ранее определен, то он определяется с помощью алгоритма ниже, что приводит к H.

    2. Пусть H' — хост гнезда C, если хост гнезда C был ранее определен. Если хост гнезда C не был ранее определен, то он определяется с помощью алгоритма ниже, что приводит к H'.

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

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

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

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

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

    В противном случае, разрешение символической ссылки завершается успешно. Пусть H — разрешенный класс или интерфейс. Хост гнезда M определяется по следующим правилам:

    • Если выполняется любое из следующих условий, то M является собственным хостом гнезда:

      • 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, либо (б) если mA объявлен в классе A, а mC объявлен в классе C, то существует метод mB, объявленный в классе B, такой, что C является подклассом B, а B — подклассом A, и mC может переопределить mB, а mB может переопределить mA.

Часть (б) последнего случая допускает "транзитивное переопределение" методов с доступом по умолчанию. Например, при следующих объявлениях классов в пакете 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. Инициализация

Инициализация класса или интерфейса включает присвоение значений любых ConstantValue атрибутов его static полям и выполнение любого объявленного метода инициализации класса или интерфейса (§2.9.2).

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

  • Выполнения любой из инструкций Java Virtual Machine 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 Virtual Machine (§5.2).

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

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

  • Этот класс или интерфейс проверен и подготовлен, но не инициализирован.

  • Этот класс или интерфейс инициализируется определенным потоком.

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

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

Точная форма состояния инициализации остается на усмотрение реализации JVM.

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

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

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

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

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

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

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

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

    Затем инициализировать каждое 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, пометить C как ошибочный, уведомить все ожидающие потоки, освободить LC и завершить аварийно, выбросив то же исключение, которое возникло при инициализации S.

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

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

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

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

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

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

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

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

5.7. Завершение работы Java Virtual Machine

Java Virtual Machine выполняет код в потоках (§2.5). Поток является либо потоком, не являющимся демоном, демоном, либо обработчиком завершения.

Читатели могут обратиться к спецификациям API Thread и Runtime для получения подробностей о том, как потоки получают статус демона и как регистрируются обработчики завершения.

Поток завершается, если либо (i) его метод run завершается успешно, либо (ii) его метод run завершается аварийно, а соответствующий обработчик необработанных исключений (§2.10) завершается успешно или аварийно. Без кода для выполнения, поток завершил выполнение и, следовательно, не имеет текущего метода (§2.5.1).

Java Virtual Machine завершается, когда произошло одно из следующих событий:

  1. Поток вызвал System.exit или Runtime.exit, и все обработчики завершения, которые впоследствии были запущены Java Virtual Machine, если таковые имеются, завершились.

  2. Поток вызвал Runtime.halt. (В этом случае обработчики завершения не запускаются.)

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

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

  4. Произошло внешнее событие, которое реализация Java Virtual Machine не может обработать. (В этом случае обработчики завершения не запускаются.)

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

При завершении Java Virtual Machine любой поток-демон или поток, не являющийся демоном, который ещё не завершился, не выполнит дальнейший Java-код. Текущий метод потока не завершается ни успешно, ни аварийно.

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

Приложения нативных языков могут использовать JNI Invocation API для создания и уничтожения Java Virtual Machine таким образом, что Java-программа, начавшая выполнение в методе main начального класса (JLS §12.1), завершает работу, когда все её потоки, не являющиеся демонами, завершатся (JLS §12.8). 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