Глава 5. Загрузка, связывание и инициализация
Оглавление
- 5.1. Пул констант во время выполнения
- 5.2. Запуск виртуальной машины Java
- 5.3. Создание и загрузка
- 5.4. Связывание
- 5.5. Инициализация
- 5.6. Связывание реализаций нативных методов
- 5.7. Выход виртуальной машины Java
Виртуальная машина Java динамически загружает, связывает и инициализирует классы и интерфейсы. Загрузка — это процесс поиска двоичного представления типа класса или интерфейса с определенным именем и создание класса или интерфейса из этого двоичного представления. Связывание — это процесс взятия класса или интерфейса и объединения его в состояние во время выполнения виртуальной машины Java, чтобы его можно было выполнить. Инициализация класса или интерфейса состоит из выполнения метода инициализации класса или интерфейса <clinit> (§2.9).
В этой главе §5.1 описывает, как виртуальная машина Java выводит символические ссылки из двоичного представления класса или интерфейса. §5.2 объясняет, как процессы загрузки, связывания и инициализации первоначально запускаются виртуальной машиной Java. §5.3 определяет, как двоичные представления классов и интерфейсов загружаются загрузчиками классов и как создаются классы и интерфейсы. Связывание описано в §5.4. §5.5 подробно описывает, как инициализируются классы и интерфейсы. §5.6 вводит понятие связывания нативных методов. Наконец, §5.7 описывает, когда виртуальная машина Java завершается.
END_OF_DOCUMENT_MARKERВиртуальная машина Java поддерживает пул констант (§2.5.5), структуру данных во время выполнения, выполняющую многие функции таблицы символов в реализации традиционного языка программирования.
Таблица constant_pool (§4.4) в двоичном представлении класса или интерфейса используется для построения пула констант во время выполнения при создании класса или интерфейса (§5.3). Все ссылки в пуле констант первоначально являются символическими. Символические ссылки в пуле констант выводятся из структур в двоичном представлении класса или интерфейса следующим образом:
-
Символическая ссылка на класс или интерфейс выводится из структуры
CONSTANT_Class_info(§4.4.1) в двоичном представлении класса или интерфейса. Такая ссылка указывает имя класса или интерфейса в форме, возвращаемой методомClass.getName, то есть:-
Для не массивно-типизированного класса или интерфейса имя является двоичным именем (§4.2.1) класса или интерфейса.
-
Для массивно-типизированного класса с n измерениями имя начинается с n символов 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) в двоичном представлении класса или интерфейса. -
Символическая ссылка на спецификатор места вызова выводится из структуры
CONSTANT_InvokeDynamic_info(§4.4.10) в двоичном представлении класса или интерфейса. Такая ссылка указывает:-
символическую ссылку на обработчик метода, который будет использоваться в качестве метода-запуска для инструкции invokedynamic (§invokedynamic);
-
последовательность символических ссылок (на классы, типы методов и обработчики методов), строковые литералы и значения констант во время выполнения, которые будут использоваться в качестве статических аргументов для метода-запуска;
-
имя метода и описатель метода.
-
Кроме того, некоторые значения во время выполнения, которые не являются символическими ссылками, выводятся из элементов, найденных в таблице constant_pool:
-
Строковый литерал — ссылка на экземпляр класса
Stringи выводится из структурыCONSTANT_String_info(§4.4.3) в двоичном представлении класса или интерфейса. СтруктураCONSTANT_String_infoуказывает последовательность точек кодов Юникода, составляющих строковый литерал.Язык программирования Java требует, чтобы идентичные строковые литералы (то есть литералы, содержащие одну и ту же последовательность точек кода) ссылались на один и тот же экземпляр класса
String(JLS §3.10.5). Кроме того, если для любой строки вызвать методString.intern, результат будет ссылкой на тот же экземпляр класса, который был бы возвращён, если бы эта строка появилась как литерал. Таким образом, следующее выражение должно иметь значениеtrue:("a" + "b" + "c").intern() == "abc"Для вывода строкового литерала виртуальная машина Java проверяет последовательность точек кодов, заданную структурой
CONSTANT_String_info.-
Если метод
String.internбыл ранее вызван для экземпляра классаString, содержащего последовательность точек кодов Юникода, идентичную той, что задана структуройCONSTANT_String_info, то результатом вывода строкового литерала будет ссылка на тот же экземпляр классаString. -
В противном случае создаётся новый экземпляр класса
String, содержащий последовательность точек кодов Юникода, заданную структуройCONSTANT_String_info; ссылкой на этот экземпляр класса является результат вывода строкового литерала. Наконец, вызывается методinternнового экземпляра классаString.
-
-
Значения констант во время выполнения выводятся из структур
CONSTANT_Integer_info,CONSTANT_Float_info,CONSTANT_Long_infoилиCONSTANT_Double_info(§4.4.4, §4.4.5) в двоичном представлении класса или интерфейса.Обратите внимание, что структуры
CONSTANT_Float_infoпредставляют значения в формате IEEE 754 с одинарной точностью, а структурыCONSTANT_Double_infoпредставляют значения в формате IEEE 754 с двойной точностью (§4.4.4, §4.4.5). Следовательно, значения констант во время выполнения, выводятся из этих структур, должны быть значениями, которые могут быть представлены с помощью форматов IEEE 754 одинарной и двойной точности соответственно.
Остальные структуры в таблице constant_pool двоичного представления класса или интерфейса — структуры CONSTANT_NameAndType_info и CONSTANT_Utf8_info (§4.4.6, §4.4.7) — используются только косвенно при выводе символических ссылок на классы, интерфейсы, методы, поля, типы методов и обработчики методов, а также при выводе строковых литералов и спецификаторов мест вызова.
Виртуальная машина Java начинает выполнение, создавая начальный класс, который определяется способом, зависящим от реализации, с помощью загрузчика начального класса (§5.3.1). Затем виртуальная машина Java связывает начальный класс, инициализирует его и вызывает метод класса public под названием void main(String[]). Вызов этого метода управляет всем дальнейшим выполнением. Выполнение инструкций виртуальной машины Java, составляющих метод main, может привести к связыванию (и, следовательно, созданию) дополнительных классов и интерфейсов, а также вызову дополнительных методов.
В реализации виртуальной машины Java начальный класс можно предоставить в качестве аргумента командной строки. В качестве альтернативы, реализация может предоставить начальный класс, который настраивает загрузчик класса, который, в свою очередь, загружает приложение. Другие варианты начального класса возможны, при условии, что они согласуются с указаниями, приведенными в предыдущем абзаце.
Создание класса или интерфейса C, обозначаемого именем N, состоит из построения в области методов виртуальной машины Java (§2.5.4) специфичной для реализации внутренней репрезентации C. Создание класса или интерфейса инициируется другим классом или интерфейсом D, который ссылается на C через пул постоянных значений. Создание класса или интерфейса также может быть инициировано вызовом D методов определённых библиотек классов платформы Java SE (§2.12), таких как рефлексия.
Если C не является массивом, он создаётся путём загрузки двоичной репрезентации C (§4) с помощью загрузчика классов. Классы массивов не имеют внешней двоичной репрезентации; они создаются виртуальной машиной Java, а не загрузчиком классов.
Существует два типа загрузчиков классов: загрузчик базовых классов, предоставляемый виртуальной машиной Java, и пользовательские загрузчики классов. Каждый пользовательский загрузчик классов является экземпляром подкласса абстрактного класса ClassLoader. Приложения используют пользовательские загрузчики классов, чтобы расширить способ, которым виртуальная машина Java динамически загружает и тем самым создаёт классы. Пользовательские загрузчики классов могут использоваться для создания классов, происходящих из пользовательских источников. Например, класс может быть загружен по сети, сгенерирован на лету или извлечён из зашифрованного файла.
Загрузчик классов L может создать C, определив его напрямую или делегировав другому загрузчику классов. Если L создаёт C напрямую, мы говорим, что L определяет C или, что то же самое, что L является определяющим загрузчиком C.
Когда один загрузчик классов делегирует другому загрузчику классов, загрузчик, который инициирует загрузку, не обязательно является тем же загрузчиком, который завершает загрузку и определяет класс. Если L создаёт C, либо напрямую, либо путём делегирования, мы говорим, что L инициирует загрузку C или, что то же самое, что L является инициирующим загрузчиком C.
Во время выполнения, класс или интерфейс определяется не только по имени, но и по паре: его двоичное имя (§4.2.1) и его определяющий загрузчик классов. Каждый такой класс или интерфейс принадлежит к одному временному пакету. Временной пакет класса или интерфейса определяется именем пакета и определяющим загрузчиком класса или интерфейса.
Виртуальная машина Java использует одну из трёх процедур для создания класса или интерфейса C, обозначаемого N:
-
Если
Nобозначает класс или интерфейс, не являющийся массивом, используется один из двух следующих методов для загрузки и создания C: -
В противном случае
Nобозначает класс массива. Класс массива создаётся непосредственно виртуальной машиной Java (§5.3.3), а не загрузчиком классов. Однако, определяющий загрузчик D используется в процессе создания класса массива C.
Если во время загрузки класса возникла ошибка, то экземпляр подкласса LinkageError должен быть сгенерирован в точке программы, которая (прямо или косвенно) использует загружаемый класс или интерфейс.
Если виртуальная машина Java пытается загрузить класс C во время проверки (§5.4.1) или разрешения (§5.4.3) (но не инициализации (§5.5)), и загрузчик классов, который используется для инициации загрузки C, выбрасывает экземпляр ClassNotFoundException, то виртуальная машина Java должна сгенерировать экземпляр NoClassDefFoundError, причиной которого является экземпляр ClassNotFoundException.
(Важно, что рекурсивная загрузка классов для загрузки суперклассов выполняется в рамках разрешения (§5.3.5, шаг 3). Поэтому ClassNotFoundException, возникающий из-за того, что загрузчик классов не смог загрузить суперкласс, должен быть обернут в NoClassDefFoundError.)
Корректно работающий загрузчик классов должен поддерживать три свойства:
-
При одинаковом имени, хороший загрузчик классов всегда должен возвращать тот же самый
Classобъект. -
Если загрузчик классов
L1делегирует загрузку класса C другому загрузчикуL2, то для любого типа T, который является непосредственным суперклассом или непосредственным суперинтерфейсом C, или типом поля в C, или типом формального параметра метода или конструктора в C, или типом возвращаемого значения метода в C,L1иL2должны возвращать тот же самыйClassобъект. -
Если пользовательский загрузчик классов предварительно загружает двоичные представления классов и интерфейсов или загружает группу связанных классов вместе, то он должен отражать ошибки загрузки только в тех точках программы, где они могли возникнуть без предварительной загрузки или групповой загрузки.
Иногда мы будем представлять класс или интерфейс с помощью обозначения <N, Ld>, где N обозначает имя класса или интерфейса, а Ld обозначает определяющий загрузчик класса или интерфейса.
Мы также будем представлять класс или интерфейс с помощью обозначения NLi, где N обозначает имя класса или интерфейса, а Li обозначает инициирующий загрузчик класса или интерфейса.
Для загрузки и создания не массива класса или интерфейса C, обозначаемого N, с помощью загрузчика базовых классов, используются следующие шаги.
Сначала виртуальная машина Java определяет, был ли загрузчик базовых классов уже записан как инициирующий загрузчик класса или интерфейса, обозначаемого N. Если да, этот класс или интерфейс является C, и создание класса не требуется.
В противном случае виртуальная машина Java передаёт аргумент N вызову метода загрузчика базовых классов для поиска предполагаемого представления C в зависимости от платформы. Обычно класс или интерфейс будет представлен файлом в иерархической файловой системе, а имя класса или интерфейса будет закодировано в пути к файлу.
Обратите внимание, что нет гарантии, что найденное предполагаемое представление является допустимым или представляет C. Эта фаза загрузки должна обнаруживать следующие ошибки:
-
Если предполагаемое представление C не найдено, загрузка генерирует исключение типа
ClassNotFoundException.
Затем виртуальная машина Java пытается получить класс, обозначаемый N, используя загрузчик базовых классов из предполагаемого представления с помощью алгоритма, описанного в §5.3.5. Этот класс является C.
Следующие шаги используются для загрузки и, следовательно, создания класса или интерфейса C (не массив), обозначаемого как N, с помощью пользовательского загрузчика классов L.
Сначала виртуальная машина Java определяет, был ли L уже записан как инициализирующий загрузчик класса или интерфейса, обозначаемого как N. Если да, то этот класс или интерфейс является C, и создание класса не требуется.
В противном случае виртуальная машина Java вызывает loadClass( у N)L. Возвращаемое значение вызова — созданный класс или интерфейс C. Затем виртуальная машина Java записывает, что L является инициализирующим загрузчиком C (§5.3.4). Остальная часть этого раздела описывает этот процесс более подробно.
Когда метод loadClass загрузчика классов L вызывается с именем N загружаемого класса или интерфейса C, L должен выполнить одну из следующих двух операций для загрузки C:
-
Загрузчик классов
Lможет создать массив байтов, представляющих C, как байты структурыClassFile(§4.1); затем он должен вызвать методdefineClassклассаClassLoader. ВызовdefineClassзаставляет виртуальную машину Java вывести класс или интерфейс, обозначаемый какN, используяLиз массива байтов по алгоритму, описанному в §5.3.5. -
Загрузчик классов
Lможет делегировать загрузку C другому загрузчику классовL'. Это делается путем передачи аргументаNнепосредственно или косвенно в вызов метода вL' (обычно методloadClass). Результат вызова — C.
В любом случае (1) или (2), если загрузчик классов L по какой-либо причине не может загрузить класс или интерфейс, обозначаемый как N, он должен выбросить экземпляр ClassNotFoundException.
С версии JDK 1.1 реализация виртуальной машины Java Oracle вызывает метод loadClass загрузчика классов, чтобы вызвать загрузку класса или интерфейса. Аргументом для loadClass является имя загружаемого класса или интерфейса. Также существует двухаргументная версия метода loadClass, где второй аргумент — boolean, указывающий, следует ли связывать класс или интерфейс. Только двухаргументная версия была предоставлена в JDK 1.0.2, и реализация виртуальной машины Java Oracle полагалась на нее для связи загруженного класса или интерфейса. Начиная с JDK 1.1, реализация виртуальной машины Java Oracle связывает класс или интерфейс напрямую, не полагаясь на загрузчик классов.
Следующие шаги используются для создания класса массива C, обозначаемого как N, с помощью загрузчика классов L. Загрузчик классов L может быть либо загрузчиком начального класса, либо пользовательским загрузчиком.
Если L уже записан как инициализирующий загрузчик класса массива с тем же типом компонента, что и N, этот класс является C, и создание класса массива не нужно.
В противном случае для создания C выполняются следующие шаги:
-
Если тип компонента — тип
reference, алгоритм этого раздела (§5.3) применяется рекурсивно с использованием загрузчика классовLдля загрузки и, следовательно, создания типа компонента C. -
Виртуальная машина Java создает новый класс массива с указанным типом компонента и количеством измерений.
Если тип компонента — тип
reference, C помечается как определенный определяющим загрузчиком типа компонента. В противном случае C помечается как определенный загрузчиком начального класса.В любом случае виртуальная машина Java затем записывает, что
Lявляется инициализирующим загрузчиком для C (§5.3.4).Если тип компонента — тип
reference, доступность класса массива определяется доступностью его типа компонента. В противном случае доступность класса массива —public.
Для обеспечения безопасной связи типов в присутствии загрузчиков классов требуется особая внимательность. Возможно, когда два разных загрузчика классов инициируют загрузку класса или интерфейса, обозначаемого как 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 по объектно-ориентированному программированию, системам, языкам и приложениям).
Следующие шаги используются для вывода объекта Class для не массивнового класса или интерфейса C, обозначаемого N, с помощью загрузчика L из предполагаемого представления в формате файла class.
-
Сначала виртуальная машина Java определяет, уже ли она зафиксировала, что
Lявляется инициатором загрузки класса или интерфейса, обозначаемогоN. Если да, эта попытка создания недействительна, и загрузка вызываетLinkageError. -
В противном случае виртуальная машина Java пытается разобрать предполагаемое представление. Однако предполагаемое представление может на самом деле не быть допустимым представлением C.
Этот этап загрузки должен обнаружить следующие ошибки:
-
Если предполагаемое представление не является структурой
ClassFile(§4.1, §4.8), загрузка вызывает экземплярClassFormatError. -
В противном случае, если предполагаемое представление не поддерживает старшую или младшую версию (§4.1), загрузка вызывает экземпляр
UnsupportedClassVersionError.UnsupportedClassVersionError, подклассClassFormatError, был введен для облегчения идентификацииClassFormatError, вызванного попыткой загрузить класс, чье представление использует неподдерживаемую версию файла форматаclass. В релизе JDK 1.1 и более ранних версиях в случае неподдерживаемой версии вызывался экземплярNoClassDefFoundErrorилиClassFormatError, в зависимости от того, загружал ли класс системный загрузчик или пользовательский загрузчик. -
В противном случае, если предполагаемое представление фактически не представляет класс с именем
N, загрузка вызывает экземплярNoClassDefFoundErrorили экземпляр одного из его подклассов.
-
-
Если у C есть непосредственный суперкласс, символическая ссылка от C к его непосредственному суперклассу разрешается с помощью алгоритма §5.4.3.1. Обратите внимание, что если C является интерфейсом, у него должен быть
Objectв качестве непосредственного суперкласса, который должен быть уже загружен. ТолькоObjectне имеет непосредственного суперкласса.Любые исключения, которые могут быть выброшены из-за разрешения класса или интерфейса, могут быть выброшены в результате этого этапа загрузки. Кроме того, на этом этапе загрузки должны быть обнаружены следующие ошибки:
-
Если класс или интерфейс, указанный как непосредственный суперкласс C, на самом деле является интерфейсом, загрузка вызывает
IncompatibleClassChangeError. -
В противном случае, если какой-либо из суперклассов C является самим C, загрузка вызывает
ClassCircularityError.
-
-
Если у C есть какие-либо непосредственные суперинтерфейсы, символические ссылки от C к его непосредственным суперинтерфейсам разрешаются с помощью алгоритма §5.4.3.1.
Любые исключения, которые могут быть выброшены из-за разрешения класса или интерфейса, могут быть выброшены на этом этапе загрузки. Кроме того, этот этап загрузки должен обнаружить следующие ошибки:
-
Если какой-либо из классов или интерфейсов, указанных в качестве непосредственных суперинтерфейсов C, фактически не является интерфейсом, загрузка вызывает
IncompatibleClassChangeError. -
В противном случае, если любой из суперинтерфейсов C является самим C, загрузка вызывает
ClassCircularityError.
-
-
Виртуальная машина Java помечает C как имеющий
Lв качестве определяющего загрузчика класса и записывает, чтоLявляется инициатором загрузки C (§5.3.4).
Связывание класса или интерфейса включает проверку и подготовку этого класса или интерфейса, его непосредственного суперкласса, его непосредственных суперинтерфейсов и типа его элементов (если это массивный тип), при необходимости. Разрешение символических ссылок в классе или интерфейсе является необязательной частью связывания.
Этот спецификация предоставляет реализации гибкость в выборе времени выполнения операций связывания (и, из-за рекурсии, загрузки), при условии соблюдения всех следующих свойств:
-
Класс или интерфейс полностью загружается перед связыванием.
-
Класс или интерфейс полностью проверяется и подготавливается перед инициализацией.
-
Ошибки, обнаруженные во время связывания, выбрасываются в момент в программе, когда выполняется какое-либо действие, которое может непосредственно или косвенно потребовать связывания с классом или интерфейсом, вызвавшим ошибку.
Например, реализация 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 будет типом элемента (§2.4) Tr.
Для i = 1 до n: Если Tfi не массивный тип, пусть Ti будет Tfi; в противном случае, пусть Ti будет типом элемента (§2.4) Tfi.
Тогда TiL1 = TiL2 для i = 0 до n.
Кроме того, если C реализует метод m, объявленный в суперинтерфейсе <I, L3> C, но сам C не объявляет метод m, то пусть <D, L2> - суперкласс C, который объявляет реализацию метода m, унаследованного C. Java Virtual Machine накладывает следующие ограничения:
С учётом того, что тип возвращаемого значения m - Tr, а типы формальных параметров m - Tf1, ..., Tfn:
Если Tr не массивный тип, пусть T0 будет Tr; в противном случае, пусть T0 будет типом элемента (§2.4) Tr.
Для i = 1 до n: Если Tfi не массивный тип, пусть Ti будет Tfi; в противном случае, пусть Ti будет типом элемента (§2.4) Tfi.
Тогда TiL2 = TiL3 для i = 0 до n.
Подготовка может произойти в любое время после создания, но должна быть завершена до инициализации.
Инструкции виртуальной машины Java anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield и putstatic используют символические ссылки на пул постоянных времени выполнения. Выполнение любой из этих инструкций требует разрешения символической ссылки.
Разрешение — это процесс динамического определения конкретных значений из символических ссылок в пуле постоянных времени выполнения.
Разрешение символической ссылки одной инструкции invokedynamic не подразумевает, что та же символическая ссылка считается разрешенной для любой другой инструкции invokedynamic.
Для всех остальных инструкций выше, разрешение символической ссылки одной инструкции подразумевает, что та же символическая ссылка считается разрешенной для любой другой инструкции, не являющейся invokedynamic.
(Вышесказанное подразумевает, что конкретное значение, определенное разрешением для конкретной инструкции invokedynamic, является объектом места вызова, связанным с этой конкретной инструкцией invokedynamic.)
Разрешение может быть предпринято для символической ссылки, которая уже была разрешена. Попытка разрешить символическую ссылку, которая уже успешно была разрешена, всегда завершается успешно тривиальным образом и всегда приводит к тому же объекту, что и начальное разрешение этой ссылки.
Если при разрешении символической ссылки произошла ошибка, то в программе в точке, которая (прямо или косвенно) использует символическую ссылку, должен быть брошен экземпляр IncompatibleClassChangeError (или подкласс).
Если попытка виртуальной машины Java разрешить символическую ссылку завершается неудачей из-за исключения, являющегося экземпляром LinkageError (или подкласса), то последующие попытки разрешения ссылки всегда завершаются той же ошибкой, которая была сгенерирована в результате первоначальной попытки разрешения.
Символическая ссылка на спецификатор места вызова конкретной инструкции invokedynamic не должна быть разрешена до выполнения этой инструкции.
В случае неудачного разрешения инструкции invokedynamic метод-загрузчик не повторно выполняется при последующих попытках разрешения.
Некоторые из перечисленных выше инструкций требуют дополнительных проверок связывания при разрешении символических ссылок. Например, для успешного разрешения инструкции getfield символической ссылки на поле, на котором она работает, необходимо не только выполнить шаги разрешения поля, указанные в §5.4.3.2, но и проверить, что поле не является static. Если это поле static, должно быть выброшено исключение связывания.
Заметим, что для успешного разрешения инструкции invokedynamic символической ссылки на спецификатор места вызова, метод-загрузчик, указанный в ней, должен завершиться нормально и вернуть подходящий объект места вызова. Если метод-загрузчик завершается неожиданно или возвращает неподходящий объект места вызова, должно быть выброшено исключение связывания.
Исключения связывания, сгенерированные проверками, специфичными для выполнения определённой инструкции виртуальной машины Java, описаны в описании этой инструкции и не рассматриваются в этом общем обсуждении разрешения. Обратите внимание, что такие исключения, хотя и описаны как часть выполнения инструкций виртуальной машины Java, а не разрешения, всё же правильно рассматриваются как неудачи разрешения.
В следующих разделах описывается процесс разрешения символической ссылки в пуле постоянных времени выполнения (§5.1) класса или интерфейса D. Подробности разрешения различаются в зависимости от типа разрешаемой символической ссылки.
Для разрешения неразрешенной символической ссылки из D на класс или интерфейс C, обозначенный N, выполняются следующие шаги:
-
Используется загрузчик класса определения D для создания класса или интерфейса, обозначенного
N. Этот класс или интерфейс является C. Подробности процесса приведены в §5.3.Любое исключение, которое может быть выброшено в результате неудачи создания класса или интерфейса, может быть выброшено в результате неудачи разрешения класса и интерфейса.
-
Если C является классом массива, а его тип элемента —
referenceтип, то символическая ссылка на класс или интерфейс, представляющий тип элемента, разрешается путем рекурсивного вызова алгоритма в §5.4.3.1. -
Наконец, проверяются разрешения доступа к C:
-
Если C недоступен (§5.4.4) для D, разрешение класса или интерфейса выбрасывает
IllegalAccessError.Это условие может возникнуть, например, если C — это класс, который изначально был объявлен
public, но был изменён на не-publicпосле компиляции D.
-
Если шаги 1 и 2 успешны, но шаг 3 завершается неудачей, C всё ещё действителен и пригоден для использования. Тем не менее, разрешение завершается неудачей, и D запрещено обращаться к C.
Для разрешения неразрешенной символической ссылки из D на поле в классе или интерфейсе C, сначала необходимо разрешить символическую ссылку на C с помощью ссылки на поле (§5.4.3.1). Поэтому любое исключение, которое может быть выброшено из-за неудачи разрешения ссылки на класс или интерфейс, может быть выброшено в результате разрешения поля. Если ссылка на C может быть успешно разрешена, может быть выброшено исключение, связанное с неудачей разрешения самой ссылки на поле.
При разрешении ссылки на поле сначала осуществляется поиск указанного поля в C и его суперклассах:
-
Если C объявляет поле с именем и описанием, указанными в ссылке на поле, поиск поля завершается успешно. Объявленное поле является результатом поиска поля.
-
В противном случае поиск поля применяется рекурсивно к прямым суперинтерфейсам указанного класса или интерфейса C.
-
В противном случае, если C имеет суперкласс S, поиск поля применяется рекурсивно к S.
-
В противном случае поиск поля завершается неудачей.
Затем:
-
Если поиск поля завершается неудачей, разрешение поля выбрасывает
NoSuchFieldError. -
В противном случае, если поиск поля завершается успешно, но указанное поле недоступно (§5.4.4) для D, разрешение поля выбрасывает
IllegalAccessError. -
В противном случае, пусть
<E,L1>— класс или интерфейс, в котором фактически объявлено указанное поле, и пустьL2— загрузчик определения D.Учитывая, что тип указанного поля Tf, пусть T будет Tf, если Tf не является массивом, и пусть T будет типом элемента (§2.4) Tf в противном случае.
Виртуальная машина Java должна наложить ограничение загрузки, что T
L1= TL2(§5.3.4).
Для разрешения неразрешенной символьной ссылки из D на метод в классе C сначала разрешается символьная ссылка на C, заданная ссылкой на метод (§5.4.3.1). Поэтому любое исключение, которое может быть возбуждено в результате неудачи при разрешении ссылки на класс, может быть возбуждено в результате разрешения метода. Если ссылка на C может быть успешно разрешена, могут быть возбуждены исключения, относящиеся к самому разрешению ссылки на метод.
При разрешении ссылки на метод:
-
Разрешение метода проверяет, является ли C классом или интерфейсом.
-
Если C является интерфейсом, разрешение метода возбуждает исключение
IncompatibleClassChangeError.
-
-
Разрешение метода пытается найти указанный метод в C и его суперклассах:
-
Если C объявляет ровно один метод с именем, заданным ссылкой на метод, и объявление является методом полиморфной подписи (§2.9), то поиск метода завершается успешно. Все имена классов, упомянутые в дескрипторе, разрешаются (§5.4.3.1).
Разрешенный метод — это объявление метода полиморфной подписи. Нет необходимости для C объявлять метод с дескриптором, заданным ссылкой на метод.
-
В противном случае, если C объявляет метод с именем и дескриптором, указанными в ссылке на метод, поиск метода завершается успешно.
-
В противном случае, если у C есть суперкласс, шаг 2 поиска метода рекурсивно вызывается для непосредственного суперкласса C.
-
-
В противном случае, поиск метода пытается найти указанный метод в одном из суперинтерфейсов указанного класса C.
-
Если любой суперинтерфейс C объявляет метод с именем и дескриптором, заданными ссылкой на метод, поиск метода завершается успешно.
-
В противном случае, поиск метода завершается неудачей.
-
Затем:
-
Если поиск метода завершается неудачей, разрешение метода возбуждает исключение
NoSuchMethodError. -
В противном случае, если поиск метода завершается успешно, и метод является
abstract, но C не являетсяabstract, разрешение метода возбуждает исключениеAbstractMethodError. -
В противном случае, если поиск метода завершается успешно, но указанный метод недоступен (§5.4.4) для D, разрешение метода возбуждает исключение
IllegalAccessError. -
В противном случае, пусть
<E,L1>будет классом или интерфейсом, в котором фактически объявлен указанный методm, и пустьL2будет определяющим загрузчиком D.Учитывая, что возвращаемый тип
mявляется Tr, а формальные типы параметровmявляются Tf1, ..., Tfn, тогда:Если Tr не является типом массива, пусть T0 будет Tr; в противном случае пусть T0 будет типом элемента (§2.4) типа Tr.
Для i = 1 до n: Если Tfi не является типом массива, пусть Ti будет Tfi; в противном случае пусть Ti будет типом элемента (§2.4) типа Tfi.
Виртуальная машина Java должна наложить ограничения загрузки Ti
L1= TiL2для i = 0 до n (§5.3.4).
Для разрешения неразрешенной символьной ссылки из D на метод интерфейса в интерфейсе C сначала разрешается символьная ссылка на C, заданная ссылкой на метод интерфейса (§5.4.3.1). Поэтому любое исключение, которое может быть возбуждено в результате неудачи при разрешении ссылки на интерфейс, может быть возбуждено в результате разрешения метода интерфейса. Если ссылка на C может быть успешно разрешена, могут быть возбуждены исключения, относящиеся к самому разрешению ссылки на метод интерфейса.
При разрешении ссылки на метод интерфейса:
-
Если C не является интерфейсом, разрешение метода интерфейса возбуждает исключение
IncompatibleClassChangeError. -
В противном случае, если указанный метод не имеет того же имени и дескриптора, что и метод в C или в одном из суперинтерфейсов C, или в классе
Object, разрешение метода интерфейса возбуждает исключениеNoSuchMethodError. -
В противном случае, пусть
<E,L1>будет классом или интерфейсом, в котором фактически объявлен указанный метод интерфейсаm, и пустьL2будет определяющим загрузчиком D.Учитывая, что возвращаемый тип
mявляется Tr, а формальные типы параметровmявляются Tf1, ..., Tfn, тогда:Если Tr не является типом массива, пусть T0 будет Tr; в противном случае пусть T0 будет типом элемента (§2.4) типа Tr.
Для i = 1 до n: Если Tfi не является типом массива, пусть Ti будет Tfi; в противном случае пусть Ti будет типом элемента (§2.4) типа Tfi.
Виртуальная машина Java должна наложить ограничения загрузки Ti
L1= TiL2для i = 0 до n (§5.3.4).
Для разрешения неразрешенной символической ссылки на тип метода все символические ссылки на классы, упомянутые в описателе метода, инкапсулированном типом метода, разрешаются (§5.4.3.1). Следовательно, любое исключение, которое может быть вызвано в результате неудачи разрешения ссылки на класс, может быть вызвано в результате разрешения типа метода.
Результатом разрешения типа метода является reference экземпляра java.lang.invoke.MethodType, который представляет собой описание метода.
Разрешение неразрешенной символической ссылки на дескриптор метода более сложно. Каждый дескриптор метода, разрешенный виртуальной машиной Java, имеет эквивалентную последовательность инструкций, называемую его поведением байткода, указанную типом дескриптора метода. Целочисленные значения и описания девяти типов дескриптора метода приведены в таблице 5.1.
Символические ссылки последовательностью инструкций на поля или методы указываются как C.x:T, где x и T — имя и описание (§4.3.2, §4.3.3) поля или метода, а C — класс или интерфейс, в котором находится поле или метод.
Таблица 5.1. Поведение байткода для дескрипторов метода
| Тип | Описание | Интерпретация |
|---|---|---|
| 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.) -
Пусть C — символическая ссылка на тип, на который ссылается R.
(C получена из
CONSTANT_Classструктуры, на которую ссылается элементclass_indexэлементаCONSTANT_Fieldref,CONSTANT_MethodrefилиCONSTANT_InterfaceMethodref, представленного R.) -
Пусть
fилиm— имя поля или метода, на которое ссылается R.(
fилиmполучены изCONSTANT_NameAndTypeструктуры, на которую ссылается элементname_and_type_indexэлементаCONSTANT_Fieldref,CONSTANT_MethodrefилиCONSTANT_InterfaceMethodrefструктуры, из которой получена R.) -
Пусть T и (в случае метода) A* — тип возвращаемого значения и последовательность типов аргументов поля или метода, на которые ссылается R.
(T и A* получены из
CONSTANT_NameAndTypeструктуры, на которую ссылается элементname_and_type_indexэлементаCONSTANT_Fieldref,CONSTANT_MethodrefилиCONSTANT_InterfaceMethodrefструктуры, из которой получена R.)
Для разрешения MH все символические ссылки на классы, поля и методы в поведении байткода MH разрешаются (§5.4.3.1, §5.4.3.2, §5.4.3.3, §5.4.3.4). То есть C, f, m, T и A* разрешаются. Следовательно, любое исключение, которое может быть вызвано в результате неудачи разрешения символической ссылки на класс, поле, метод или метод интерфейса, может быть вызвано в результате разрешения дескриптора метода.
(В общем случае разрешение дескриптора метода может быть выполнено в тех же обстоятельствах, что и успешное разрешение виртуальной машиной Java символических ссылок в поведении байткода. В частности, дескрипторы методов для private и protected членов могут быть созданы именно в тех классах, для которых соответствующие обычные обращения являются законными.)
Если все такие символические ссылки могут быть разрешены, то reference экземпляра java.lang.invoke.MethodType получается так же, как при разрешении символической ссылки на описание метода (§4.3.3), заданного для типа MH в таблице 5.2.
Таблица 5.2. Описатели методов для дескрипторов методов
| Тип | Описание | Описатель метода |
|---|---|---|
| 1 | REF_getField | (C)T |
| 2 | REF_getStatic | ()T |
| 3 | REF_putField | (C,T)V |
| 4 | REF_putStatic | (T)V |
| 5 | REF_invokeVirtual | (C,A*)T |
| 6 | REF_invokeStatic | (A*)T |
| 7 | REF_invokeSpecial | (C,A*)T |
| 8 | REF_newInvokeSpecial | (A*)C |
| 9 | REF_invokeInterface | (C,A*)T |
Результатом разрешения дескриптора метода является reference o экземпляра java.lang.invoke.MethodHandle, который представляет дескриптор метода MH. Если метод m имеет установленный флаг ACC_VARARGS (§4.6), то o — это дескриптор метода с переменным числом аргументов; в противном случае o — это дескриптор метода с фиксированным числом аргументов.
(Дескриптор метода с переменным числом аргументов выполняет упаковку списка аргументов (JLS §15.12.4.2) при вызове через invoke, а его поведение относительно invokeExact такое же, как если бы флаг ACC_VARARGS не был установлен.)
Разрешение дескриптора метода вызывает исключение IncompatibleClassChangeError, если m имеет установленный флаг ACC_VARARGS, и либо последовательность типов аргументов m пуста, или последний параметр в последовательности типов аргументов m не является типом массива. (То есть создание дескриптора метода с переменным числом аргументов терпит неудачу.)
Описание типа экземпляра java.lang.invoke.MethodHandle, на который ссылается o, — это экземпляр java.lang.invoke.MethodType, полученный при разрешении типа метода, упомянутом ранее.
(Описание типа дескриптора метода таково, что допустимый вызов invokeExact в java.lang.invoke.MethodHandle для дескриптора метода имеет точно такие же эффекты на стеке, как поведение байткода. Вызов этого дескриптора метода с допустимым набором аргументов имеет точно такой же эффект и возвращает такой же результат (если таковой имеется), как и соответствующее поведение байткода.)
Реализация виртуальной машины Java не обязана интернировать типы методов или дескрипторы методов. То есть две различные символические ссылки на типы методов или дескрипторы методов, которые структурно идентичны, могут не разрешаться в один и тот же экземпляр java.lang.invoke.MethodType или java.lang.invoke.MethodHandle соответственно.
Класс java.lang.invoke.MethodHandles в API платформы Java SE позволяет создавать дескрипторы методов без поведения байткода. Их поведение определяется методом java.lang.invoke.MethodHandles, который их создаёт. Например, дескриптор метода может, при вызове, сначала применить преобразования к значениям аргументов, затем передать преобразованные значения для вызова другого дескриптора метода, затем применить преобразование к значению, возвращённому из этого вызова, а затем вернуть преобразованное значение в качестве собственного результата.
Для разрешения неразрешенной символической ссылки на спецификатор места вызова требуется три шага:
-
Спецификатор места вызова предоставляет символическую ссылку на обработчик метода, который должен служить методом инициализации для динамического места вызова. Обработчик метода разрешается (§5.4.3.5), чтобы получить ссылку на экземпляр
java.lang.invoke.MethodHandle. -
Спецификатор места вызова предоставляет описатель метода, TD. Ссылка на экземпляр
java.lang.invoke.MethodTypeполучается так же, как при разрешении символической ссылки на тип метода (§5.4.3.5) с теми же параметрами и возвращаемым типом, что и TD. -
Спецификатор места вызова предоставляет ноль или более статических аргументов, которые передают метаданные, специфичные для приложения, методу инициализации. Любые статические аргументы, которые являются символическими ссылками на классы, обработчики методов или типы методов, разрешаются так же, как при вызове инструкции ldc (§ldc), для получения ссылок на
Classобъекты,java.lang.invoke.MethodHandleобъекты иjava.lang.invoke.MethodTypeобъекты соответственно. Любые статические аргументы, которые являются строковыми литералами, используются для получения ссылок наStringобъекты.
Результатом разрешения спецификатора места вызова является кортеж, состоящий из:
-
ссылка на экземпляр
referencejava.lang.invoke.MethodHandle, -
ссылка на экземпляр
referencejava.lang.invoke.MethodType, -
ссылки на экземпляры
referenceClass,java.lang.invoke.MethodHandle,java.lang.invoke.MethodTypeиString.
При разрешении символической ссылки на обработчик метода в спецификаторе места вызова, или разрешении символической ссылки на тип метода для описателя метода в спецификаторе места вызова, или разрешении символической ссылки на любой статический аргумент, могут быть брошены любые исключения, относящиеся к разрешению типа метода или обработчика метода (§5.4.3.5).
Класс или интерфейс C доступен классу или интерфейсу D только если выполняется одно из следующих условий:
-
C является
public. -
C и D являются членами одной и той же исполняющей среды (§5.3).
Поле или метод R доступен классу или интерфейсу D только если выполняется любое из следующих условий:
-
R является
public. -
R является
protectedи объявлен в классе C, и D является либо подклассом C, либо самим C. Кроме того, если R не являетсяstatic, то символическая ссылка на R должна содержать символическую ссылку на класс T, такой что T является либо подклассом D, либо суперклассом D, либо самим D. -
R имеет либо
protected, либо имеет доступ по умолчанию (то есть ниpublic, ниprotected, ниprivate), и объявлен классом в той же исполняющей среде, что и D. -
R является
privateи объявлен в D.
В этом обсуждении управления доступом опущено связанное ограничение на цель поля доступа protected или вызова метода (цель должна быть класса D или подтипа D). Это требование проверяется в процессе проверки (§5.4.1); это не входит в контроль доступа во время связывания.
Инстансный метод m1, объявленный в классе C, переопределяет другой инстансный метод m2, объявленный в классе A, если все перечисленные ниже условия выполняются:
-
C является подклассом A.
-
m2имеет то же имя и описатель, что иm1. -
Либо:
-
m2помечен какACC_PUBLIC; или помечен какACC_PROTECTED; или не помечен ни какACC_PUBLIC, ни какACC_PROTECTED, ни какACC_PRIVATE, и принадлежит к той же исполняющей среде, что и C, или -
m1переопределяет методm3,m3отличный отm1,m3отличный отm2, такой чтоm3переопределяетm2.
-
Инициализация класса или интерфейса заключается в выполнении его метода инициализации класса или интерфейса (§2.9).
Класс или интерфейс могут быть инициализированы только в результате:
-
Выполнения любой из инструкций виртуальной машины Java new, getstatic, putstatic или invokestatic, которые ссылаются на класс или интерфейс (§new, §getstatic, §putstatic, §invokestatic). Все эти инструкции ссылаются на класс напрямую или косвенно через ссылку на поле или метод.
При выполнении инструкции new указанный класс или интерфейс инициализируется, если он еще не был инициализирован.
При выполнении инструкций getstatic, putstatic или invokestatic класс или интерфейс, объявивший разрешенное поле или метод, инициализируется, если он еще не был инициализирован.
-
Первый вызов объекта
java.lang.invoke.MethodHandle, который был результатом разрешения обработчика метода виртуальной машиной Java (§5.4.3.5) и который имеет тип 2 (REF_getStatic), 4 (REF_putStatic) или 6 (REF_invokeStatic). -
Вызов определённых рефлексивных методов в библиотеке классов (§2.12), например, в классе
Classили в пакетеjava.lang.reflect. -
Инициализация одного из его подклассов.
-
Его назначение в качестве начального класса при запуске виртуальной машины 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 еще не инициализирован, рекурсивно выполнить всю эту процедуру для SC. При необходимости сначала проверить и подготовить SC.
Если инициализация SC завершается с ошибкой из-за выброшенного исключения, то получить
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 может оптимизировать эту процедуру путём исключения получения блокировки на шаге 1 (и освобождения на шаге 4/5), когда она может определить, что инициализация класса уже завершена, при условии, что, с точки зрения модели памяти Java, все упорядочения happens-before (JLS §17.4.5), которые существовали бы, если бы блокировка была получена, по-прежнему существуют, когда выполняется оптимизация.
Связывание — это процесс, посредством которого функция, написанная на языке, отличном от языка программирования Java, и реализующая метод native, интегрируется в виртуальную машину Java, чтобы ее можно было выполнить. Хотя этот процесс традиционно называется связыванием, в спецификации используется термин связывание, чтобы избежать путаницы с связыванием классов или интерфейсов виртуальной машиной Java.
Виртуальная машина Java завершает работу, когда какая-то нить вызывает метод exit класса Runtime или класса System, или метод halt класса Runtime, и операция exit или halt разрешена менеджером безопасности.
Кроме того, спецификация JNI (Java Native Interface) описывает завершение работы виртуальной машины Java, когда API вызова JNI используется для загрузки и выгрузки виртуальной машины Java.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.