Глава 5. Загрузка, связывание и инициализация
Оглавление
- 5.1. Пул постоянных времени выполнения
- 5.2. Запуск виртуальной машины Java
- 5.3. Создание и загрузка
- 5.4. Связывание
- 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.
Виртуальная машина 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 — используются косвенно при построении пула констант во время выполнения. Ни одна запись в пуле констант во время выполнения не соответствует напрямую этим структурам.
Некоторые записи в пуле констант во время выполнения являются загружаемыми, что означает:
Запись в пуле констант во время выполнения загружаема, если она получена из загружаемой записи в таблице constant_pool (см. Table 4.4-C). Соответственно, следующие записи в пуле констант во время выполнения загружаемые:
-
Символические ссылки на классы и интерфейсы
-
Символические ссылки на обработчики методов
-
Символические ссылки на типы методов
-
Символические ссылки на динамически вычисляемые константы
-
Статические константы
Виртуальная машина Java запускается, создавая начальный класс или интерфейс с помощью загрузчика базового класса (§5.3.1) или пользовательского загрузчика (§5.3.2). Затем виртуальная машина Java связывает начальный класс или интерфейс, инициализирует его и вызывает метод public static метод void main(String[]). Вызов этого метода управляет всем дальнейшим выполнением. Выполнение инструкций виртуальной машины Java, составляющих метод main, может привести к связыванию (и, следовательно, созданию) дополнительных классов и интерфейсов, а также вызову дополнительных методов.
Начальный класс или интерфейс задается способом, зависящим от реализации. Например, начальный класс или интерфейс может быть передан в качестве аргумента командной строки. В качестве альтернативы, реализация виртуальной машины Java может сама предоставить начальный класс, который настраивает загрузчик, который в свою очередь загружает приложение. Другие варианты начального класса или интерфейса возможны, если они согласуются со спецификацией, приведенной в предыдущем абзаце.
Создание класса или интерфейса 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) и его загрузчиком-определяющим. Каждый такой класс или интерфейс принадлежит одному выполняемому пакету. Выполняемый пакет класса или интерфейса определяется именем пакета и загрузчиком-определяющим для класса или интерфейса.
Процесс загрузки и создания класса или интерфейса без массива 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 завершается успешно.
Процесс загрузки и создания класса или интерфейса без массива C, обозначенного как N, с помощью пользовательского загрузчика класса L следующий.
Сначала виртуальная машина Java определяет, был ли L уже записан как инициализирующий загрузчик класса или интерфейса, обозначенного как N. Если да, то этот класс или интерфейс является C, и никакой загрузки или создания класса не требуется.
В противном случае виртуальная машина Java вызывает метод loadClass класса ClassLoader для L, передавая имя N класса или интерфейса. L должен выполнить одну из двух операций для загрузки и создания класса или интерфейса C:
-
Загрузчик класса
Lможет загрузить C напрямую. Это достигается получением массива байтов, который предполагает представление C как структурыClassFile(§4.1), и затем вызовом методаdefineClassклассаClassLoader. ВызовdefineClassзаставляет виртуальную машину Java вывести класс или интерфейс C, обозначенный какN, из массива байтов с использованиемL, и затем создать C по алгоритму из §5.3.5.Lдолжен использовать результатdefineClassв качестве результатаloadClass. -
Загрузчик класса
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 связывает класс или интерфейс напрямую, не полагаясь на загрузчик класса.
Для создания класса массива C с именем N в связи с загрузчиком класса L используются следующие шаги. L может быть либо загрузчиком класса Bootstrap, либо пользовательским загрузчиком класса.
Сначала виртуальная машина Java определяет, был ли L уже записан как инициализирующий загрузчик класса массива с тем же типом компонента, что и у N. Если да, то этот класс является C, и создание класса массива не требуется.
В противном случае для создания C выполняются следующие шаги:
-
Если тип компонента — тип
reference, то алгоритм этого раздела (§5.3) применяется рекурсивно с использованиемLдля загрузки и создания типа компонента C. -
Виртуальная машина Java создаёт новый класс массива с указанным типом компонента и числом измерений.
Если тип компонента — тип
reference, виртуальная машина Java отмечает C как имеющий определяющий загрузчик типа компонента в качестве своего определяющего загрузчика. В противном случае виртуальная машина Java отмечает C как имеющего загрузчик класса Bootstrap в качестве своего определяющего загрузчика.В любом случае виртуальная машина Java записывает, что
Lявляется инициализирующим загрузчиком для C (§5.3.4).Если тип компонента — тип
reference, доступность класса массива определяется доступностью его типа компонента (§5.4.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 по объектно-ориентированному программированию, системам, языкам и приложениям).
Загрузчики классов требуют взаимодействия с виртуальной машиной Java для получения и создания класса или интерфейса из двоичного представления, предоставленного загрузчиком (§5.3.1, §5.3.2). Следующие шаги используются для получения класса или интерфейса C, обозначенного N, из предполагаемого представления в формате файла class по запросу загрузчика классов L.
-
Сначала виртуальная машина Java определяет, разрешено ли загрузчику классов
Lполучить класс или интерфейс, обозначенныйN.Если
Lуже был записан как инициализирующий загрузчик класса или интерфейса, обозначенногоN, получение вызовет исключениеLinkageError.Если виртуальная машина Java уже в процессе получения класса или интерфейса, обозначенного
N, по запросу загрузчика классовL, получение вызовет исключениеClassCircularityError. -
Далее виртуальная машина 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.
-
-
Если у 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.
-
-
Если у 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 предоставляет механизмы для синхронизации запросов на получение и кэширования успешных результатов, чтобы не происходили избыточные запросы на получение.
Виртуальная машина 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). В любом случае класс или интерфейс рассматривается как член специального модуля во время выполнения, который неявно связан с загрузчиком классов. Этот специальный модуль во время выполнения известен как безымянный модуль загрузчика классов. Пакет во время выполнения класса или интерфейса связан с безымянным модулем загрузчика классов. Существуют специальные правила для безымянных модулей, призванные максимизировать их взаимодействие с другими модулями во время выполнения, как следует:
-
Безымянный модуль загрузчика классов отличается от всех других модулей во время выполнения, связанных с тем же загрузчиком классов.
-
Безымянный модуль загрузчика классов отличается от всех модулей во время выполнения (включая безымянные модули), связанных с другими загрузчиками классов.
-
Каждый безымянный модуль читает каждый модуль во время выполнения.
-
Каждый безымянный модуль экспортирует в каждый модуль во время выполнения каждый пакет во время выполнения, связанный с собой.
Связывание класса или интерфейса включает проверку и подготовку этого класса или интерфейса, его непосредственного суперкласса, его непосредственных суперинтерфейсов и его типа элемента (если это массив), при необходимости. Связывание также включает разрешение символических ссылок в классе или интерфейсе, хотя не обязательно одновременно с проверкой и подготовкой класса или интерфейса.
Данная спецификация предоставляет реализации гибкость в отношении времени выполнения действий связывания (и, из-за рекурсии, загрузки), при условии соблюдения следующих свойств:
-
Класс или интерфейс полностью загружается перед связыванием.
-
Класс или интерфейс полностью проверяется и подготавливается перед инициализацией.
-
Ошибки, обнаруженные во время связывания, генерируются в точке программы, где выполняется какое-либо действие, которое может напрямую или косвенно потребовать связывания с классом или интерфейсом, участвующим в ошибке.
-
Символическая ссылка на динамически вычисляемую константу не разрешается до тех пор, пока не будет выполнена инструкция ldc, ldc_w или ldc2_w, которая ссылается на нее, или (ii) не будет вызван метод бутстрапа, который ссылается на нее в качестве статического аргумента.
Символическая ссылка на динамически вычисляемую точку вызова не разрешается до тех пор, пока не будет вызван метод бутстрапа, который ссылается на нее в качестве статического аргумента.
Например, реализация Java Virtual Machine может выбрать стратегию "ленивого" связывания, где каждая символическая ссылка в классе или интерфейсе (за исключением вышеперечисленных символических ссылок) разрешается индивидуально при её использовании. В качестве альтернативы, реализация может выбрать стратегию "жадного" связывания, где все символические ссылки разрешаются сразу, когда класс или интерфейс проверяется. Это означает, что процесс разрешения может продолжаться в некоторых реализациях после инициализации класса или интерфейса. Какой бы стратегии не была выбрана, любая ошибка, обнаруженная во время разрешения, должна быть сгенерирована в точке программы, которая (прямо или косвенно) использует символическую ссылку на класс или интерфейс.
Поскольку связывание включает выделение новых структур данных, оно может завершиться ошибкой с OutOfMemoryError.
Проверка (§4.10) гарантирует, что двоичное представление класса или интерфейса структурно верно (§4.9). Проверка может привести к загрузке дополнительных классов и интерфейсов (§5.3), но не обязательно к их проверке или подготовке.
Если двоичное представление класса или интерфейса не удовлетворяет статическим или структурным ограничениям, перечисленным в §4.9, то должна быть сгенерирована ошибка VerifyError в точке программы, которая привела к проверке класса или интерфейса.
Если попытка Java Virtual Machine проверить класс или интерфейс завершается ошибкой, которая является экземпляром LinkageError (или подклассом), то последующие попытки проверки класса или интерфейса всегда завершаются той же ошибкой, которая была сгенерирована в результате первоначальной попытки проверки.
Подготовка включает создание статических полей для класса или интерфейса и инициализацию этих полей их значениями по умолчанию (§2.3, §2.4). Это не требует выполнения кода Java Virtual Machine; явные инициализаторы для статических полей выполняются как часть инициализации (§5.5), а не подготовки.
Во время подготовки класса или интерфейса C, Java Virtual Machine также накладывает ограничения на загрузку (§5.3.4):
-
Пусть
L1- определяющая загрузчик C. Для каждого метода экземпляраm, объявленного в C, который может переопределять (§5.4.5) метод экземпляра, объявленный в суперклассе или суперинтерфейсе D =<N2,L2>, для каждого имени класса или интерфейсаN, указанного в дескриптореm(§4.3.3), Java Virtual Machine накладывает ограничение на загрузкуNL1=NL2. -
Для каждого метода экземпляра
m, объявленного в суперинтерфейсе I =<N3,L3>, если C не объявляет метод экземпляра, который может переопределятьm, то выбирается метод (§5.4.6) относительно C и методаmв I. Пусть D =<N2,L2>класс или интерфейс, который объявляет выбранный метод. Для каждого имени класса или интерфейсаN, указанного в дескриптореm, Java Virtual Machine накладывает ограничение на загрузкуNL2=NL3.
Подготовка может происходить в любое время после создания, но должна быть завершена до инициализации.
Многие инструкции виртуальной машины 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, а не разрешения, по-прежнему считаются ошибками разрешения.
Для разрешения неразрешенной символической ссылки из D на класс или интерфейс C, обозначенный как N, выполняются следующие шаги:
-
Используется загрузчик, определяющий D, для загрузки и, следовательно, создания класса или интерфейса, обозначенного как
N. Этот класс или интерфейс - C. Подробности процесса приведены в §5.3.Любое исключение, которое может быть сгенерировано из-за невозможности загрузить и, следовательно, создать C, может быть сгенерировано из-за ошибки разрешения класса и интерфейса.
-
Если C является классом массива, а его элементный тип - тип
reference, то символическая ссылка на класс или интерфейс, представляющий элементный тип, разрешается путем рекурсивного вызова алгоритма в §5.4.3.1. -
Наконец, применяется контроль доступа для доступа из D к C (§5.4.4).
Если шаги 1 и 2 выполнены успешно, но шаг 3 завершился неудачно, C по-прежнему допустим и пригоден для использования. Тем не менее, разрешение завершается неудачно, и D запрещено получать доступ к C.
Для разрешения неразрешенной символической ссылки от D до поля в классе или интерфейсе C, символическая ссылка на C, заданная ссылкой на поле, должна быть сначала разрешена (§5.4.3.1). Следовательно, любая исключительная ситуация, которая может быть выброшена в результате неудачи разрешения ссылки на класс или интерфейс, может быть выброшена в результате неудачи разрешения поля. Если ссылка на C может быть успешно разрешена, может быть выброшено исключение, связанное с неудачей разрешения самой ссылки на поле.
При разрешении ссылки на поле, разрешение поля сначала пытается найти указанное поле в C и его суперклассах:
-
Если C объявляет поле с именем и описателем, заданными ссылкой на поле, поиск поля завершается успешно. Объявленное поле является результатом поиска поля.
-
В противном случае, поиск поля применяется рекурсивно к непосредственным суперинтерфейсам указанного класса или интерфейса C.
-
В противном случае, если у C есть суперкласс S, поиск поля применяется рекурсивно к S.
-
В противном случае, поиск поля завершается неудачей.
Затем определяется результат разрешения поля:
-
Если поиск поля завершился неудачей, разрешение поля выбрасывает исключение
NoSuchFieldError. -
В противном случае, поиск поля завершился успешно. Для доступа из D к полю, являющемуся результатом поиска поля, применяется контроль доступа (§5.4.4). Затем:
-
Если контроль доступа завершился неудачей, разрешение поля завершается неудачей по той же причине.
-
В противном случае, контроль доступа завершился успешно. Налагаются ограничения загрузки, следующим образом.
Пусть
<E,L1>- это класс или интерфейс, в котором фактически объявлено указанное поле. ПустьL2- определяющий загрузчик D.Для любого имени класса или интерфейса
N, упомянутого в описателе указанного поля (§4.3.2), виртуальная машина Java накладывает ограничение загрузкиNL1=NL2(§5.3.4).Если наложение этого ограничения приводит к нарушению каких-либо ограничений загрузки, то разрешение поля завершается неудачей. В противном случае, разрешение поля завершается успешно.
-
Для разрешения неразрешенной символической ссылки из D на метод в классе C, сначала разрешается символическая ссылка на C, заданная ссылкой на метод (§5.4.3.1). Следовательно, любое исключение, которое может быть выброшено в результате неудачи разрешения ссылки на класс, может быть выброшено в результате неудачи разрешения метода. Если ссылка на C может быть успешно разрешена, могут быть выброшены исключения, связанные с самим разрешением ссылки на метод.
При разрешении ссылки на метод:
-
Если C является интерфейсом, разрешение метода выбросит
IncompatibleClassChangeError. -
В противном случае разрешение метода пытается найти указанный метод в C и его суперклассах:
-
Если C объявляет ровно один метод с именем, указанным в ссылке на метод, и объявление является методом с полиморфной сигнатурой (§2.9.3), тогда поиск метода завершается успешно. Дескриптор, указанный в ссылке на метод, разрешается, как при разрешении неразрешенной символической ссылки на тип метода (§5.4.3.5).
Разрешенный метод — это объявление метода с полиморфной сигнатурой. Необязательно, чтобы C объявлял метод с дескриптором, указанным в ссылке на метод.
-
В противном случае, если C объявляет метод с именем и дескриптором, указанными в ссылке на метод, поиск метода завершается успешно.
-
В противном случае, если у C есть суперкласс, шаг 2 разрешения метода рекурсивно вызывается для непосредственного суперкласса C.
-
-
В противном случае, разрешение метода пытается найти указанный метод в суперинтерфейсах указанного класса 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, противоречило бы не детерминированному выбору методов суперинтерфейсов. Вместо этого разрешение предполагает, что класс времени выполнения вызываемого объекта имеет конкретную реализацию метода.
Для разрешения неразрешённой символьной ссылки из D на метод интерфейса в интерфейсе C сначала разрешается символьная ссылка на C, заданная ссылкой на метод интерфейса (§5.4.3.1). Поэтому любое исключение, которое может быть выброшено в результате неудачи разрешения ссылки на интерфейс, может быть выброшено в результате неудачи разрешения метода интерфейса. Если ссылка на C может быть успешно разрешена, могут быть выброшены исключения, связанные с самим разрешением ссылки на метод интерфейса.
При разрешении ссылки на метод интерфейса:
-
Если C не является интерфейсом, разрешение метода интерфейса выбросит исключение
IncompatibleClassChangeError. -
В противном случае, если C объявляет метод с именем и описателем, указанными в ссылке на метод интерфейса, поиск метода завершается успешно.
-
В противном случае, если класс
Objectобъявляет метод с именем и описателем, указанными в ссылке на метод интерфейса, у которого установлен флагACC_PUBLIC, а флагACC_STATICне установлен, поиск метода завершается успешно. -
В противном случае, если методы наиболее специфических суперинтерфейсов C для имени и описателя, указанных в ссылке на метод, включают ровно один метод, у которого не установлен флаг
ACC_ABSTRACT, то этот метод выбирается, и поиск метода завершается успешно. -
В противном случае, если любой суперинтерфейс C объявляет метод с именем и описателем, указанными в ссылке на метод, у которого не установлен ни флаг
ACC_PRIVATE, ни флагACC_STATIC, один из них выбирается произвольно, и поиск метода завершается успешно. -
В противном случае, поиск метода завершается неудачей.
Результат разрешения метода интерфейса определяется следующим образом:
-
Если поиск метода завершился неудачей, разрешение метода интерфейса выбросит исключение
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.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. |
| 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 разрешаются с помощью следующих трех шагов:
-
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. -
Следующие ограничения применяются к результату разрешения 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поле или метод.
-
-
-
Ссылка на экземпляр
java.lang.invoke.MethodTypeполучается так, как если бы происходило разрешение неразрешенной символической ссылки на тип метода, содержащего дескриптор метода, указанный в Таблице 5.4.3.5-B для типаMH.Это как если бы символическая ссылка на дескриптор метода содержала символическую ссылку на тип метода, который у разрешенного дескриптора метода будет в конечном итоге. Подробная структура типа метода получается путем проверки Таблицы 5.4.3.5-B.
Таблица 5.4.3.5-B. Дескрипторы методов для дескрипторов методов
Тип Описание Дескриптор метода 1 REF_getField(C)T2 REF_getStatic()T3 REF_putField(C,T)V4 REF_putStatic(T)V5 REF_invokeVirtual(C,A*)T6 REF_invokeStatic(A*)T7 REF_invokeSpecial(C,A*)T8 REF_newInvokeSpecial(A*)C9 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, который их создает. Например, обработчик метода может, при вызове, сначала применить преобразования к своим значениям аргументов, затем передать преобразованные значения в вызов другого обработчика метода, затем применить преобразование к значению, возвращенному этим вызовом, затем вернуть преобразованное значение в качестве своего собственного результата.
Для разрешения неразрешенной символической ссылки R на динамически вычисленную константу или сайт вызова необходимо выполнить три задачи. Во-первых, R анализируется для определения кода, который будет служить его методом загрузки, и аргументов, которые будут переданы этому коду. Во-вторых, аргументы упаковываются в массив, и вызывается метод загрузки. В-третьих, результат метода загрузки проверяется и используется в качестве результата разрешения.
Первая задача включает следующие шаги:
-
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. По историческим причинам, обработчик метода загрузки для динамически вычисляемого сайта вызова аналогично не ограничен. -
Если R — это символическая ссылка на динамически вычисленную константу, то она указывает на описание поля.
Если описание поля указывает на примитивный тип, то получается
referenceна предопределенный объектClass, представляющий этот тип (см. методisPrimitiveв классеClass).В противном случае описание поля указывает на класс или интерфейс, или тип массива. Получается
referenceна объектClass, представляющий тип, указанный в описании поля, как если бы это было разрешение неразрешенной символической ссылки на класс или интерфейс (§5.4.3.1), имя которого соответствует типу, указанному в описании поля.Любое исключение, которое может быть выброшено в результате неудачи при разрешении символической ссылки на класс или интерфейс, может быть выброшено на этом шаге.
-
Если R — это символическая ссылка на динамически вычисляемый сайт вызова, то она указывает на описание метода.
Получается
referenceна экземплярjava.lang.invoke.MethodType, как если бы это было разрешение неразрешенной символической ссылки на тип метода (§5.4.3.5) с теми же типами параметров и возвращаемых значений, что и в описании метода.Любое исключение, которое может быть выброшено в результате неудачи при разрешении символической ссылки на тип метода, может быть выброшено на этом шаге.
-
R указывает на ноль или более статических аргументов, которые передают метаданные, специфичные для приложения, методу загрузки. Каждый статический аргумент A разрешается в порядке, заданном R, следующим образом:
-
Если A — строковая константа, то получается
referenceна экземпляр классаString. -
Если A — числовая константа, то получается
referenceна объект, представляющий число, по следующей процедуре:-
Пусть
v— значение числовой константы, а T — описание поля, соответствующее типу числовой константы. -
Пусть
MH— обработчик метода, созданный как если бы вызывался методidentityклассаjava.lang.invoke.MethodHandlesс аргументом, представляющим классObject. -
Получается
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.Любое исключение, которое может быть выброшено в результате неудачи при разрешении символической ссылки, может быть выброшено на этом шаге.
-
Вторая задача, вызов обработчика метода загрузки, включает следующие шаги:
-
Массив выделяется с типом компонента
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 может быть способна пропустить выделение массива и, без изменения наблюдаемого поведения, напрямую передать аргументы в метод загрузки.
-
Обработчик метода загрузки вызывается, как если бы был вызов
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.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 — один и тот же класс или интерфейс, то тест гнездового элемента успешен.
-
В противном случае выполняются следующие шаги в порядке следования:
-
Пусть H — хост гнезда D, если хост гнезда D был ранее определен. Если хост гнезда D не был ранее определен, то он определяется с помощью алгоритма ниже, что приводит к H.
-
Пусть H' — хост гнезда C, если хост гнезда C был ранее определен. Если хост гнезда C не был ранее определен, то он определяется с помощью алгоритма ниже, что приводит к H'.
-
Сравниваются 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.
-
Метод экземпляра 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.
Во время выполнения инструкции invokeinterface или invokevirtual выбирается метод с учетом (i) типа объекта во стеке во время выполнения и (ii) метода, который был ранее разрешен инструкцией. Правила выбора метода относительно класса или интерфейса C и метода mR следующие:
-
Если
mRпомечен какACC_PRIVATE, то это выбранный метод. -
В противном случае выбранный метод определяется следующей процедурой поиска:
-
Если C содержит объявление метода экземпляра
m, который может переопределитьmR(§5.4.5), тоmявляется выбранным методом. -
В противном случае, если у C есть суперкласс, выполняется поиск объявления метода экземпляра, который может переопределить
mR, начиная с непосредственного суперкласса C и продолжая с непосредственного суперкласса этого класса и так далее, пока не будет найден метод или не останется больше суперклассов. Если метод найден, он является выбранным методом. -
В противном случае определяются максимально-специфичные методы суперинтерфейсов C (§5.4.3.3). Если ровно один соответствует имени и описанию
mRи не помечен какabstract, то это выбранный метод.Любой максимально-специфичный метод суперинтерфейса, выбранный на этом шаге, может переопределить
mR; нет необходимости проверять это явно.
-
Хотя C обычно является классом, при применении этих правил он может быть интерфейсом во время подготовки (§5.4.2).
Инициализация класса или интерфейса включает присвоение значений любых 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 затем такова:
-
Синхронизироваться на блокировке инициализации,
LC, для C. Это включает ожидание, пока текущий поток сможет получитьLC. -
Если состояние инициализации C указывает, что инициализация C выполняется другим потоком, то освободить
LCи заблокировать текущий поток до получения сообщения о завершении инициализации, после чего повторить эту процедуру.Состояние прерывания потока не затрагивается выполнением процедуры инициализации.
-
Если состояние инициализации C указывает, что инициализация C выполняется текущим потоком, то это рекурсивный запрос инициализации. Освободить
LCи завершить обычным образом. -
Если состояние инициализации C указывает, что C уже был инициализирован, то никаких дальнейших действий не требуется. Освободить
LCи завершить обычным образом. -
Если состояние инициализации C находится в ошибочном состоянии, то инициализация невозможна. Освободить
LCи выброситьNoClassDefFoundError. -
В противном случае, записать факт, что инициализация C выполняется текущим потоком, и освободить
LC.Затем инициализировать каждое
staticполе C постоянным значением из его атрибутаConstantValue(§4.7.2) в порядке появления полей в структуреClassFile. -
Далее, если C — класс, а не интерфейс, то пусть SC — его суперкласс, а SI1, ..., SIn — все его суперинтерфейсы (прямые и косвенные), объявляющие хотя бы один не-
abstract, не-staticметод. Порядок суперинтерфейсов задаётся рекурсивным перечислением иерархии суперинтерфейсов каждого интерфейса, непосредственно реализованного C. Для каждого интерфейса I, непосредственно реализованного C (в порядке массиваinterfacesC), перечисление повторяется для суперинтерфейсов I (в порядке массиваinterfacesI) перед возвращением I.Для каждого S в списке [ SC, SI1, ..., SIn ], если S ещё не инициализирован, то рекурсивно выполнить всю эту процедуру для S. При необходимости, предварительно проверить и подготовить S.
Если инициализация S завершилась аварийно из-за выброшенного исключения, то получить
LC, пометить C как ошибочный, уведомить все ожидающие потоки, освободитьLCи завершить аварийно, выбросив то же исключение, которое возникло при инициализации S. -
Далее, определить, включены ли утверждения для C путём запроса к его определяющему загрузчику.
-
Затем, если C объявляет метод инициализации класса или интерфейса, выполнить этот метод.
-
Если выполнение метода инициализации класса или интерфейса завершилось успешно или если C не объявляет метода инициализации класса или интерфейса, то получить
LC, пометить C как полностью инициализированный, уведомить все ожидающие потоки, освободитьLCи завершить процедуру успешно. -
В противном случае, метод инициализации класса или интерфейса должен завершиться аварийно, выбросив исключение E. Если класс E не является
Errorили одним из его подклассов, то создать новый экземпляр классаExceptionInInitializerErrorс E в качестве аргумента и использовать этот объект вместо E на следующем шаге. Если новый экземплярExceptionInInitializerErrorне может быть создан из-заOutOfMemoryError, то использовать объектOutOfMemoryErrorвместо E на следующем шаге. -
Получить
LC, пометить C как ошибочный, уведомить все ожидающие потоки, освободитьLCи завершить эту процедуру аварийно по причине E или его замены, как определено на предыдущем шаге.
Реализация Java Virtual Machine может оптимизировать эту процедуру, исключая получение блокировки на шаге 1 (и освобождение на шаге 4/5), когда она может определить, что инициализация класса уже завершена, при условии, что все happens-before упорядочивания (JLS §17.4.5), которые существовали бы, если бы блокировка была получена, всё ещё существуют, когда выполняется оптимизация.
Связывание — это процесс интеграции функции, написанной на языке, отличном от Java, и реализующей метод native, в Java Virtual Machine, чтобы её можно было выполнить. Хотя этот процесс традиционно называется линковкой, в спецификации используется термин связывание, чтобы избежать путаницы с линковкой классов или интерфейсов Java Virtual Machine.
Java Virtual Machine выполняет код в потоках (§2.5). Поток является либо потоком, не являющимся демоном, демоном, либо обработчиком завершения.
Читатели могут обратиться к спецификациям API Thread и Runtime для получения подробностей о том, как потоки получают статус демона и как регистрируются обработчики завершения.
Поток завершается, если либо (i) его метод run завершается успешно, либо (ii) его метод run завершается аварийно, а соответствующий обработчик необработанных исключений (§2.10) завершается успешно или аварийно. Без кода для выполнения, поток завершил выполнение и, следовательно, не имеет текущего метода (§2.5.1).
Java Virtual Machine завершается, когда произошло одно из следующих событий:
-
Поток вызвал
System.exitилиRuntime.exit, и все обработчики завершения, которые впоследствии были запущены Java Virtual Machine, если таковые имеются, завершились. -
Поток вызвал
Runtime.halt. (В этом случае обработчики завершения не запускаются.) -
Реализация Java Virtual Machine распознала внешний сигнал, требующий завершения Java Virtual Machine, и все обработчики завершения, которые впоследствии были запущены Java Virtual Machine, если таковые имеются, завершились.
Природа события находится за пределами данной спецификации, но она обязательно должна быть чем-то, что Java Virtual Machine может надёжно обработать. Примером является получение сигнала от операционной системы.
-
Произошло внешнее событие, которое реализация 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.