Глава 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, упомянутое в дескрипторе поля или метода, обозначало один и тот же класс или интерфейс при загрузке загрузчиком L1 и при загрузке загрузчиком L2.
Для обеспечения этого виртуальная машина Java накладывает ограничения загрузки вида NL1 = NL2 во время подготовки (§5.4.2) и разрешения (§5.4.3). Для соблюдения этих ограничений виртуальная машина Java в определённые моменты (см. §5.3.1, §5.3.2, §5.3.3 и §5.3.5) регистрирует, что определённый загрузчик является инициализирующим загрузчиком определённого класса. После регистрации загрузчика в качестве инициализирующего загрузчика класса, виртуальная машина Java должна немедленно проверить, не нарушаются ли какие-либо ограничения загрузки. Если они нарушены, запись отменяется, виртуальная машина Java выбрасывает исключение LinkageError, и операция загрузки, которая привела к регистрации, завершается неудачей.
Аналогично, после наложения ограничения загрузки (см. §5.4.2, §5.4.3.2, §5.4.3.3 и §5.4.3.4), виртуальная машина Java должна немедленно проверить, не нарушаются ли какие-либо ограничения загрузки. Если они нарушены, недавно наложенное ограничение загрузки отменяется, виртуальная машина Java выбрасывает исключение LinkageError, и операция, которая привела к наложению ограничения (разрешение или подготовка, в зависимости от случая), завершается неудачей.
Описанные ситуации являются единственными моментами, когда виртуальная машина Java проверяет, нарушены ли какие-либо ограничения загрузки. Ограничение загрузки нарушается тогда и только тогда, когда выполняются все следующие четыре условия:
-
Существует загрузчик
L, такой чтоLбыл зарегистрирован виртуальной машиной Java как инициализирующий загрузчик класса C с именемN. -
Существует загрузчик
L', такой чтоL' был зарегистрирован виртуальной машиной Java как инициализирующий загрузчик класса C ' с именемN. -
Отношение эквивалентности, определённое (транзитивным замыканием) набора наложенных ограничений, подразумевает
NL=NL'. -
C ≠ C '.
Полное обсуждение загрузчиков классов и безопасности типов выходит за рамки данной спецификации. Для более подробного обсуждения читатели могут обратиться к работе Динамическая загрузка классов в виртуальной машине Java Шенга Лянга и Гилада Брахи (Труды конференции ACM SIGPLAN 1998 по объектно-ориентированному программированию, системам, языкам и приложениям).
Следующие шаги используются для вывода класса или интерфейса C, не являющегося массивом, обозначаемого N, из предполагаемого представления в формате файла class с использованием загрузчика классов L.
-
Сначала виртуальная машина Java определяет, был ли
Lуже записан как инициализирующий загрузчик класса или интерфейса, обозначаемогоN. Если да, то эта попытка вывода недействительна, и вывод вызывает исключениеLinkageError. -
В противном случае виртуальная машина 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. Обратите внимание, что если C является интерфейсом, то у него должен быть непосредственный суперкласс
Object, который уже должен быть загружен. ТолькоObjectне имеет непосредственного суперкласса.Любое исключение, которое может быть выброшено в результате неудачи разрешения класса или интерфейса, может быть выброшено в результате вывода. Кроме того, вывод должен обнаруживать следующие проблемы:
-
Если любой из суперклассов C равен C самому себе, вывод вызывает исключение
ClassCircularityError. -
В противном случае, если класс или интерфейс, названный непосредственным суперклассом 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.
Любое исключение, которое может быть выброшено в результате неудачи разрешения класса или интерфейса, может быть выброшено в результате вывода. Кроме того, вывод должен обнаруживать следующие проблемы:
-
Если любой из суперинтерфейсов C равен C самому себе, вывод вызывает исключение
ClassCircularityError. -
В противном случае, если любой класс или интерфейс, названный непосредственным суперинтерфейсом 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 поддерживает организацию классов и интерфейсов в модули. Принадлежность класса или интерфейса 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) не вызывается метод bootstrap, который ссылается на него как на статический аргумент.
Символьная ссылка на динамически вычисляемую точку вызова не разрешается до тех пор, пока не вызывается метод bootstrap, который ссылается на него как на статический аргумент.
Например, реализация 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,L2>, Java Virtual Machine накладывает ограничения на загрузку следующим образом.Учитывая, что тип возвращаемого значения
m- Tr, а типы формальных параметровm- Tf1, ..., Tfn:Если Tr не является типом массива, пусть T0 будет Tr; в противном случае, пусть T0 будет типом элемента Tr.
Для i = 1 до n: Если Tfi не является типом массива, пусть Ti будет Tfi; в противном случае, пусть Ti будет типом элемента Tfi.
Тогда Ti
L1= TiL2для i = 0 до n. -
Для каждого метода экземпляра
m, объявленного в суперинтерфейсе<I,L3>от C, если C сам не объявляет метод экземпляра, который может переопределятьm, то выбирается метод (§5.4.6) относительно C и методаmв<I,L3>. Пусть<D,L2>- класс или интерфейс, который объявляет выбранный метод. Java Virtual Machine накладывает ограничения на загрузку следующим образом.Учитывая, что тип возвращаемого значения
m- Tr, а типы формальных параметровm- Tf1, ..., Tfn:Если Tr не является типом массива, пусть T0 будет Tr; в противном случае, пусть T0 будет типом элемента Tr.
Для i = 1 до n: Если Tfi не является типом массива, пусть Ti будет Tfi; в противном случае, пусть Ti будет типом элемента Tfi.
Тогда Ti
L2= TiL3для i = 0 до n.
Подготовка может происходить в любое время после создания, но должна быть завершена до инициализации.
Многие инструкции виртуальной машины 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. Учитывая, что тип указанного поля — Tf: если Tf не является типом массива, пусть T будет Tf; в противном случае, пусть T будет элементом типа Tf.Виртуальная машина Java накладывает ограничение загрузки, что T
L1= TL2.Если наложение этого ограничения приводит к нарушению каких-либо ограничений загрузки (§5.3.4), то разрешение поля завершается неудачей. В противном случае, разрешение поля выполняется успешно.
-
Для разрешения неразрешенной символической ссылки из D на метод в классе C, сначала разрешается символическая ссылка на C, задаваемая ссылкой на метод (§5.4.3.1). Поэтому любое исключение, которое может быть выброшено в результате неудачи разрешения ссылки на класс, может быть выброшено в результате неудачи разрешения метода. Если ссылка на C может быть успешно разрешена, могут быть выброшены исключения, связанные с самим разрешением ссылки на метод.
При разрешении ссылки на метод:
-
Если C является интерфейсом, разрешение метода выбрасывает
IncompatibleClassChangeError. -
В противном случае разрешение метода пытается найти указанный метод в C и его суперклассах:
-
Если C объявляет ровно один метод с именем, указанным в ссылке на метод, и объявление является полиморфным методом по сигнатуре (§2.9.3), то поиск метода успешен. Все имена классов, упомянутые в описателе, разрешаются (§5.4.3.1).
Разрешенный метод — это объявление полиморфного метода по сигнатуре. Необходимо, чтобы 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. Учитывая, что возвращаемый типm— Tr, а типы формальных параметровm— Tf1, ..., Tfn:Если Tr не является типом массива, пусть T0 будет Tr; в противном случае пусть T0 будет типом элемента Tr.
Для i = 1 до n: Если Tfi не является типом массива, пусть Ti будет Tfi; в противном случае пусть Ti будет типом элемента Tfi.
Виртуальная машина Java накладывает ограничения загрузки Ti
L1= TiL2для i = 0 до n.Если наложение этих ограничений приводит к нарушению каких-либо ограничений загрузки (§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, поиск метода выполняется успешно. -
В противном случае, если методы максимально-специфичных суперинтерфейсов (§5.4.3.3) интерфейса C для имени и описания, указанных в ссылке на метод, включают ровно один метод, у которого не установлен флаг
ACC_ABSTRACT, то этот метод выбирается и поиск метода выполняется успешно. -
В противном случае, если любой суперинтерфейс C объявляет метод с именем и описанием, указанными в ссылке на метод, у которого не установлен ни флаг
ACC_PRIVATE, ни флагACC_STATIC, один из них произвольно выбирается и поиск метода выполняется успешно. -
В противном случае, поиск метода завершается неудачей.
Результат разрешения метода интерфейса определяется следующим образом:
-
Если поиск метода завершился неудачей, разрешение метода интерфейса выбросит исключение
NoSuchMethodError. -
В противном случае, поиск метода завершился успешно. Применяется контроль доступа для доступа от D к методу, который является результатом поиска метода (§5.4.4). Затем:
-
Если контроль доступа завершился неудачей, разрешение метода интерфейса завершается неудачей по той же причине.
-
В противном случае, контроль доступа выполняется успешно. Налагаются ограничения на загрузку, как указано ниже.
Пусть
<E,L1>— класс или интерфейс, в котором фактически объявлен ссылаемый метод интерфейсаm. ПустьL2— определяющая загрузчик D. Учитывая, что тип возвращаемого значенияm— Tr, а типы формальных параметровm— Tf1, ..., Tfn:Если Tr не является типом массива, пусть T0 — Tr; в противном случае, пусть T0 — элементный тип Tr.
Для i = 1 до n: Если Tfi не является типом массива, пусть Ti — Tfi; в противном случае, пусть Ti — элементный тип Tfi.
Виртуальная машина Java накладывает ограничения на загрузку Ti
L1= TiL2для i = 0 до n.Если наложение этих ограничений приводит к нарушению каких-либо ограничений на загрузку (§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 получена из
CONSTANT_Fieldref,CONSTANT_MethodrefилиCONSTANT_InterfaceMethodrefструктуры, на которую ссылается элементreference_indexэлементаCONSTANT_MethodHandle, из которого полученMH.Например, R — символическая ссылка на C
.fдля поведения байткода типа 1 и символическая ссылка на C.<init>для поведения байткода типа 8.Если поведение байткода
MHимеет тип 7 (REF_invokeSpecial), то C должен быть текущим классом или интерфейсом, суперклассом текущего класса, непосредственным суперинтерфейсом текущего класса или интерфейса илиObject. -
Пусть T — тип поля, на которое ссылается R, или тип возвращаемого значения метода, на который ссылается R. Пусть A* — последовательность (возможно, пустая) типов параметров метода, на который ссылается R.
T и A* получены из
CONSTANT_NameAndTypeструктуры, на которую ссылается элементname_and_type_indexвCONSTANT_Fieldref,CONSTANT_MethodrefилиCONSTANT_InterfaceMethodrefструктуре, из которой получен R.
Для разрешения MH все символические ссылки на классы, интерфейсы, поля и методы в поведении байткода MH разрешаются с помощью следующих четырех шагов:
-
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имеет вид 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.
-
-
-
Разрешение происходит как при разрешении неразрешённых символических ссылок на классы и интерфейсы, имена которых соответствуют каждому типу в A*, и типу T, в этом порядке.
-
Ссылка на экземпляр
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 и 4 любое исключение, которое может быть вызвано в результате неудачи разрешения символической ссылки на класс, интерфейс, поле или метод, может быть вызвано в результате неудачи разрешения обработчика метода. На шаге 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к экземпляруjava.lang.invoke.MethodHandleс помощью следующей процедуры:-
Пусть
v— значение числовой константы, и пусть T — дескриптор поля, который соответствует типу числовой константы. -
Пусть
MH— обработчик метода, полученный как при вызове методаidentityклассаjava.lang.invoke.MethodHandlesс аргументом, представляющим классObject. -
Получается
referenceк экземпляруjava.lang.invoke.MethodHandle, как при вызовеMH.invoke(v)с дескриптором метода(T)Ljava/lang/Object;.
-
-
Если A — символическая ссылка на динамически вычисляемую константу с дескриптором поля, указывающим на примитивный тип T, то A разрешается, получая примитивное значение
v. Используяvи T, получаетсяreferenceк экземпляруjava.lang.invoke.MethodHandleсогласно процедуре, описанной выше для числовых констант. -
Если A — любой другой тип символической ссылки, то результатом является результат разрешения A.
Среди символических ссылок в пуле постоянных времени выполнения, символические ссылки на динамически вычисляемые константы являются особыми, поскольку они получены из записей
constant_pool, которые могут синтаксически ссылаться на сами себя через атрибутBootstrapMethods(§4.7.23). Однако виртуальная машина Java не поддерживает разрешение символической ссылки на динамически вычисляемую константу, зависящую от самой себя (то есть, в качестве статического аргумента для своего собственного метода инициализации). Таким образом, когда и R, и A — символические ссылки на динамически вычисляемые константы, если A совпадает с R или A предоставляет статический аргумент, который (прямо или косвенно) ссылается на R, то разрешение завершается неудачей сStackOverflowErrorв точке, где потребовалось бы повторное разрешение R.В отличие от инициализации класса (§5.5), где циклы разрешены между неинициализированными классами, разрешение не допускает циклов в символических ссылках на динамически вычисляемые константы. Если реализация разрешения использует рекурсивный стек, то
StackOverflowErrorпроизойдёт естественным образом. Если нет, то реализация должна обнаружить цикл, а не, скажем, бесконечно циклиться или возвращать значение по умолчанию для динамически вычисляемой константы.Аналогичный цикл может возникнуть, если тело метода инициализации ссылается на динамически вычисляемую константу, которая в настоящее время разрешается. Это всегда было возможно для инициализаций invokedynamic, и не требует особого обращения при разрешении; рекурсивные
invokeWithArgumentsвызовы естественным образом приведут кStackOverflowError.Любое исключение, которое может быть выброшено в результате неудачи разрешения символической ссылки, может быть выброшено на этом шаге.
-
Вторая задача, вызов обработчика метода инициализации, включает следующие шаги:
-
Массив выделяется с типом компонентов
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 любого метода не накладывается для вызова.
Если несколько потоков пытаются разрешить R одновременно, метод загрузки может быть вызван одновременно. Поэтому методы загрузки, которые обращаются к глобальным данным приложения, должны принимать меры предосторожности против гонок.
Управление доступом применяется во время разрешения (§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).
Инициализация класса или интерфейса состоит из выполнения его метода инициализации класса или интерфейса (§2.9.2).
Класс или интерфейс C может быть инициализирован только в результате:
-
Выполнения любой из инструкций виртуальной машины Java new, getstatic, putstatic или invokestatic, которые ссылаются на C (§new, §getstatic, §putstatic, §invokestatic).
При выполнении инструкции new, класс, который необходимо инициализировать, — это класс, на который ссылается инструкция.
При выполнении инструкций getstatic, putstatic или invokestatic класс или интерфейс, который необходимо инициализировать, — это класс или интерфейс, который объявляет разрешенный поле или метод.
-
Первый вызов экземпляра
java.lang.invoke.MethodHandle, являющегося результатом разрешения дескриптора метода (§5.4.3.5) для дескриптора метода типа 2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic) или 8 (REF_newInvokeSpecial).Это подразумевает, что класс вспомогательного метода инициализируется при вызове вспомогательного метода для инструкции invokedynamic (§invokedynamic), как часть продолжающегося разрешения спецификатора места вызова.
-
Вызов определенных рефлексивных методов в библиотеке классов (§2.12), например, в классе
Classили в пакетеjava.lang.reflect. -
Если C является классом, инициализация одного из его подклассов.
-
Если C является интерфейсом, который объявляет метод, не являющийся
abstract, не являющийсяstatic, инициализация класса, который реализует C напрямую или косвенно. -
Его назначение в качестве начального класса или интерфейса при запуске виртуальной машины Java (§5.2).
Перед инициализацией класс или интерфейс должен быть связан, то есть проверен, подготовлен и, при необходимости, разрешен.
Поскольку виртуальная машина Java многопоточная, инициализация класса или интерфейса требует тщательной синхронизации, так как другой поток может пытаться инициализировать тот же класс или интерфейс одновременно. Также существует возможность, что инициализация класса или интерфейса может быть запрошена рекурсивно в рамках инициализации этого класса или интерфейса. Реализация виртуальной машины Java отвечает за синхронизацию и рекурсивную инициализацию с помощью следующей процедуры. Она предполагает, что объект Class уже проверен и подготовлен, а объект Class содержит состояние, указывающее одну из четырех ситуаций:
-
Этот объект
Classпроверен и подготовлен, но не инициализирован. -
Этот объект
Classинициализируется каким-либо конкретным потоком. -
Этот объект
Classполностью инициализирован и готов к использованию. -
Этот объект
Classнаходится в ошибочном состоянии, возможно, потому что инициализация была предпринята и не удалась.
Для каждого класса или интерфейса C существует уникальная блокировка инициализации LC. Сопоставление от C к LC остается на усмотрение реализации виртуальной машины Java. Например, LC может быть объектом Class для C или монитором, связанным с этим объектом Class. Процедура инициализации C затем следующая:
-
Синхронизироваться на блокировке инициализации,
LC, для C. Это включает ожидание, пока текущий поток сможет получитьLC. -
Если объект
Classдля C указывает, что инициализация C выполняется другим потоком, то освободитьLCи заблокировать текущий поток до получения уведомления о завершении текущей инициализации, после чего повторить эту процедуру.Состояние прерывания потока не затрагивается выполнением процедуры инициализации.
-
Если объект
Classдля C указывает, что инициализация C выполняется текущим потоком, то это должен быть рекурсивный запрос на инициализацию. ОсвободитьLCи завершить обычным образом. -
Если объект
Classдля C указывает, что C уже инициализирован, то дальнейшие действия не требуются. ОсвободитьLCи завершить обычным образом. -
Если объект
Classдля C находится в ошибочном состоянии, то инициализация невозможна. ОсвободитьLCи выброситьNoClassDefFoundError. -
В противном случае, зафиксировать факт, что инициализация объекта
Classдля C выполняется текущим потоком, и освободитьLC.Затем инициализировать каждое поле
finalstaticобъекта C постоянным значением из его атрибутаConstantValue(§4.7.2), в порядке следования полей в структуреClassFile. -
Далее, если C является классом, а не интерфейсом, то пусть SC будет его суперклассом, а SI1, ..., SIn — все его суперинтерфейсы (прямые или косвенные), которые объявляют как минимум один не-
abstract, не-staticметод. Порядок суперинтерфейсов определяется рекурсивным перечислением иерархии суперинтерфейсов каждого интерфейса, непосредственно реализуемого C. Для каждого интерфейса I, непосредственно реализуемого C (в порядке массиваinterfacesобъекта C), перечисление рекурсивно выполняется для суперинтерфейсов I (в порядке массиваinterfacesобъекта I) перед возвратом I.Для каждого S в списке [ SC, SI1, ..., SIn ], если S ещё не инициализирован, то рекурсивно выполнить всю эту процедуру для S. При необходимости сначала проверить и подготовить S.
Если инициализация S завершается аварийно из-за выброшенного исключения, то получить
LC, пометить объектClassдля C как ошибочный, уведомить все ожидающие потоки, освободитьLCи завершить аварийно, сбрасывая то же исключение, которое привело к инициализации SC. -
Далее, определить, включены ли утверждения для C, запросив информацию у его определяющего загрузчика.
-
Далее, выполнить метод инициализации класса или интерфейса для C.
-
Если выполнение метода инициализации класса или интерфейса завершилось нормально, то получить
LC, пометить объектClassдля C как полностью инициализированный, уведомить все ожидающие потоки, освободитьLCи завершить эту процедуру нормально. -
В противном случае, метод инициализации класса или интерфейса должен был завершиться аварийно, выбросив исключение E. Если класс E не является
Errorили одним из его подклассов, то создать новый экземпляр классаExceptionInInitializerErrorс E в качестве аргумента и использовать этот объект вместо E на следующем шаге. Если новый экземплярExceptionInInitializerErrorне может быть создан из-за ошибкиOutOfMemoryError, то использовать объектOutOfMemoryErrorвместо E на следующем шаге. -
Получить
LC, пометить объектClassдля C как ошибочный, уведомить все ожидающие потоки, освободитьLCи завершить эту процедуру аварийно с причиной E или её заменой, определённой на предыдущем шаге.
Реализация Java Virtual Machine может оптимизировать эту процедуру, опуская получение блокировки на шаге 1 (и освобождение на шаге 4/5), когда она может определить, что инициализация класса уже завершена, при условии, что, с точки зрения модели памяти Java, все зависимости (JLS §17.4.5), которые существовали бы, если бы блокировка была получена, всё ещё существуют, когда выполняется оптимизация.
Связывание — это процесс интеграции в Java Virtual Machine функции, написанной на языке, отличном от Java programming language, и реализующей метод native, для её выполнения. Хотя этот процесс традиционно называется линковкой, в спецификации используется термин связывание, чтобы избежать путаницы с линковкой классов или интерфейсов Java Virtual Machine.
Java Virtual Machine выходит, когда какой-либо поток вызывает метод exit класса Runtime или класса System, или метод halt класса Runtime, и операция exit или halt разрешена системным менеджером безопасности.
Кроме того, спецификация JNI (Java Native Interface) описывает завершение работы Java Virtual Machine при использовании API вызова JNI для загрузки и разгрузки Java Virtual Machine.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.