Spec-Zone.ru › Java Virtual Machine Specification 7

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

Оглавление

5.1. Пул констант во время выполнения
5.2. Запуск виртуальной машины Java
5.3. Создание и загрузка
5.3.1. Загрузка с помощью загрузчика Bootstrap
5.3.2. Загрузка с помощью пользовательского загрузчика классов
5.3.3. Создание классов массивов
5.3.4. Ограничения загрузки
5.3.5. Вывод класса из представления файла class
5.4. Связывание
5.4.1. Проверка
5.4.2. Подготовка
5.4.3. Разрешение
5.4.3.1. Разрешение классов и интерфейсов
5.4.3.2. Разрешение полей
5.4.3.3. Разрешение методов
5.4.3.4. Разрешение методов интерфейса
5.4.3.5. Разрешение типа метода и дескриптора метода
5.4.3.6. Разрешение спецификатора места вызова
5.4.4. Управление доступом
5.4.5. Переопределение методов
5.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

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

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

Таблица constant_pool (§4.4) в двоичном представлении класса или интерфейса используется для построения пула констант во время выполнения при создании класса или интерфейса (§5.3). Все ссылки в пуле констант первоначально являются символическими. Символические ссылки в пуле констант выводятся из структур в двоичном представлении класса или интерфейса следующим образом:

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

    • Для не массивно-типизированного класса или интерфейса имя является двоичным именем (§4.2.1) класса или интерфейса.

    • Для массивно-типизированного класса с n измерениями имя начинается с n символов ASCII "[" и затем представляет тип элемента:

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

      • В противном случае, если тип элемента — ссылочный тип, он представляется символом ASCII "L", за которым следует двоичное имя (§4.2.1) типа элемента, и символом 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) — используются только косвенно при выводе символических ссылок на классы, интерфейсы, методы, поля, типы методов и обработчики методов, а также при выводе строковых литералов и спецификаторов мест вызова.

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

Виртуальная машина Java начинает выполнение, создавая начальный класс, который определяется способом, зависящим от реализации, с помощью загрузчика начального класса (§5.3.1). Затем виртуальная машина Java связывает начальный класс, инициализирует его и вызывает метод класса public под названием void main(String[]). Вызов этого метода управляет всем дальнейшим выполнением. Выполнение инструкций виртуальной машины Java, составляющих метод main, может привести к связыванию (и, следовательно, созданию) дополнительных классов и интерфейсов, а также вызову дополнительных методов.

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

5.3. Создание и загрузка

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

Если C не является массивом, он создаётся путём загрузки двоичной репрезентации C (§4) с помощью загрузчика классов. Классы массивов не имеют внешней двоичной репрезентации; они создаются виртуальной машиной Java, а не загрузчиком классов.

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

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

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

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

Виртуальная машина Java использует одну из трёх процедур для создания класса или интерфейса C, обозначаемого N:

  • Если N обозначает класс или интерфейс, не являющийся массивом, используется один из двух следующих методов для загрузки и создания C:

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

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

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

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

Если виртуальная машина Java пытается загрузить класс C во время проверки (§5.4.1) или разрешения (§5.4.3) (но не инициализации (§5.5)), и загрузчик классов, который используется для инициации загрузки C, выбрасывает экземпляр ClassNotFoundException, то виртуальная машина Java должна сгенерировать экземпляр NoClassDefFoundError, причиной которого является экземпляр ClassNotFoundException.

(Важно, что рекурсивная загрузка классов для загрузки суперклассов выполняется в рамках разрешения (§5.3.5, шаг 3). Поэтому ClassNotFoundException, возникающий из-за того, что загрузчик классов не смог загрузить суперкласс, должен быть обернут в NoClassDefFoundError.)

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

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

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

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

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

Мы также будем представлять класс или интерфейс с помощью обозначения NLi, где N обозначает имя класса или интерфейса, а Li обозначает инициирующий загрузчик класса или интерфейса.

5.3.1. Загрузка с помощью загрузчика базовых классов

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

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

В противном случае виртуальная машина Java передаёт аргумент N вызову метода загрузчика базовых классов для поиска предполагаемого представления C в зависимости от платформы. Обычно класс или интерфейс будет представлен файлом в иерархической файловой системе, а имя класса или интерфейса будет закодировано в пути к файлу.

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

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

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

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

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

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

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

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

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

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

В любом случае (1) или (2), если загрузчик классов L по какой-либо причине не может загрузить класс или интерфейс, обозначаемый как N, он должен выбросить экземпляр ClassNotFoundException.

С версии JDK 1.1 реализация виртуальной машины Java Oracle вызывает метод loadClass загрузчика классов, чтобы вызвать загрузку класса или интерфейса. Аргументом для loadClass является имя загружаемого класса или интерфейса. Также существует двухаргументная версия метода loadClass, где второй аргумент — boolean, указывающий, следует ли связывать класс или интерфейс. Только двухаргументная версия была предоставлена в JDK 1.0.2, и реализация виртуальной машины Java Oracle полагалась на нее для связи загруженного класса или интерфейса. Начиная с JDK 1.1, реализация виртуальной машины Java Oracle связывает класс или интерфейс напрямую, не полагаясь на загрузчик классов.

5.3.3. Создание классов массивов

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

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

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

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

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

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

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

    Если тип компонента — тип reference, доступность класса массива определяется доступностью его типа компонента. В противном случае доступность класса массива — public.

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

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

Когда класс или интерфейс C = <N1, L1> делает символическую ссылку на поле или метод другого класса или интерфейса D = <N2, L2>, символическая ссылка включает дескриптор, определяющий тип поля или возвращаемые и аргументные типы метода. Важно, чтобы любое имя типа N, упомянутое в дескрипторе поля или метода, обозначало тот же класс или интерфейс при загрузке с помощью L1 и при загрузке с помощью L2.

Для этого виртуальная машина Java накладывает ограничения загрузки вида NL1 = NL2 во время подготовки (§5.4.2) и разрешения (§5.4.3). Для соблюдения этих ограничений виртуальная машина Java в определенные моменты (см. §5.3.1, §5.3.2, §5.3.3 и §5.3.5) записывает, что определенный загрузчик является инициализирующим загрузчиком определенного класса. После записи того, что загрузчик является инициализирующим загрузчиком класса, виртуальная машина Java должна немедленно проверить, нарушаются ли какие-либо ограничения загрузки. Если это так, запись отменяется, виртуальная машина Java выбросит LinkageError, и операция загрузки, вызвавшая запись, завершается неудачно.

Аналогично, после наложения ограничения загрузки (см. §5.4.2, §5.4.3.2, §5.4.3.3 и §5.4.3.4), виртуальная машина Java должна немедленно проверить, нарушаются ли какие-либо ограничения загрузки. Если это так, недавно наложенное ограничение загрузки отменяется, виртуальная машина Java выбросит LinkageError, и операция, которая вызвала наложение ограничения (либо разрешение, либо подготовка, в зависимости от ситуации), завершается неудачно.

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

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

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

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

  • C ≠ C '.

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

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

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

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

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

    Этот этап загрузки должен обнаружить следующие ошибки:

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

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

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

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

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

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

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

    • В противном случае, если какой-либо из суперклассов C является самим C, загрузка вызывает ClassCircularityError.

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

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

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

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

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

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

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

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

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

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

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

Например, реализация Java Virtual Machine может выбрать разрешение каждой символической ссылки в классе или интерфейсе индивидуально при её использовании («ленивое» или «позднее» разрешение) или разрешить их все сразу при проверке класса («жадное» или «статическое» разрешение). Это означает, что процесс разрешения может продолжаться в некоторых реализациях после инициализации класса или интерфейса. Какой бы стратегии ни следовала, любая ошибка, обнаруженная во время разрешения, должна быть выброшена в момент в программе, который (непосредственно или косвенно) использует символическую ссылку на класс или интерфейс.

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

5.4.1. Проверка

Проверка (§4.10) гарантирует, что двоичное представление класса или интерфейса структурно правильно (§4.9). Проверка может привести к загрузке дополнительных классов и интерфейсов (§5.3), но не обязательно к их проверке или подготовке.

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

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

5.4.2. Подготовка

Подготовка включает создание статических полей для класса или интерфейса и инициализацию таких полей их значениями по умолчанию (§2.3, §2.4). Это не требует выполнения кода Java Virtual Machine; явные инициализаторы статических полей выполняются в рамках инициализации (§5.5), а не подготовки.

Во время подготовки класса или интерфейса C Java Virtual Machine также накладывает ограничения на загрузку (§5.3.4). Пусть 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.

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

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

Инструкции виртуальной машины 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. Подробности разрешения различаются в зависимости от типа разрешаемой символической ссылки.

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

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

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

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

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

  3. Наконец, проверяются разрешения доступа к C:

    • Если C недоступен (§5.4.4) для D, разрешение класса или интерфейса выбрасывает IllegalAccessError.

      Это условие может возникнуть, например, если C — это класс, который изначально был объявлен public, но был изменён на не-public после компиляции D.

Если шаги 1 и 2 успешны, но шаг 3 завершается неудачей, C всё ещё действителен и пригоден для использования. Тем не менее, разрешение завершается неудачей, и D запрещено обращаться к C.

5.4.3.2. Разрешение поля

Для разрешения неразрешенной символической ссылки из D на поле в классе или интерфейсе C, сначала необходимо разрешить символическую ссылку на C с помощью ссылки на поле (§5.4.3.1). Поэтому любое исключение, которое может быть выброшено из-за неудачи разрешения ссылки на класс или интерфейс, может быть выброшено в результате разрешения поля. Если ссылка на C может быть успешно разрешена, может быть выброшено исключение, связанное с неудачей разрешения самой ссылки на поле.

При разрешении ссылки на поле сначала осуществляется поиск указанного поля в C и его суперклассах:

  1. Если C объявляет поле с именем и описанием, указанными в ссылке на поле, поиск поля завершается успешно. Объявленное поле является результатом поиска поля.

  2. В противном случае поиск поля применяется рекурсивно к прямым суперинтерфейсам указанного класса или интерфейса C.

  3. В противном случае, если C имеет суперкласс S, поиск поля применяется рекурсивно к S.

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

Затем:

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

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

  • В противном случае, пусть <E, L1> — класс или интерфейс, в котором фактически объявлено указанное поле, и пусть L2 — загрузчик определения D.

    Учитывая, что тип указанного поля Tf, пусть T будет Tf, если Tf не является массивом, и пусть T будет типом элемента (§2.4) Tf в противном случае.

    Виртуальная машина Java должна наложить ограничение загрузки, что TL1 = TL2 (§5.3.4).

5.4.3.3. Разрешение метода

Для разрешения неразрешенной символьной ссылки из D на метод в классе C сначала разрешается символьная ссылка на C, заданная ссылкой на метод (§5.4.3.1). Поэтому любое исключение, которое может быть возбуждено в результате неудачи при разрешении ссылки на класс, может быть возбуждено в результате разрешения метода. Если ссылка на C может быть успешно разрешена, могут быть возбуждены исключения, относящиеся к самому разрешению ссылки на метод.

При разрешении ссылки на метод:

  1. Разрешение метода проверяет, является ли C классом или интерфейсом.

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

  2. Разрешение метода пытается найти указанный метод в C и его суперклассах:

    • Если C объявляет ровно один метод с именем, заданным ссылкой на метод, и объявление является методом полиморфной подписи (§2.9), то поиск метода завершается успешно. Все имена классов, упомянутые в дескрипторе, разрешаются (§5.4.3.1).

      Разрешенный метод — это объявление метода полиморфной подписи. Нет необходимости для C объявлять метод с дескриптором, заданным ссылкой на метод.

    • В противном случае, если C объявляет метод с именем и дескриптором, указанными в ссылке на метод, поиск метода завершается успешно.

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

  3. В противном случае, поиск метода пытается найти указанный метод в одном из суперинтерфейсов указанного класса 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 должна наложить ограничения загрузки TiL1 = TiL2 для i = 0 до n (§5.3.4).

5.4.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 должна наложить ограничения загрузки TiL1 = TiL2 для i = 0 до n (§5.3.4).

5.4.3.5. Разрешение типа метода и дескриптора метода

Для разрешения неразрешенной символической ссылки на тип метода все символические ссылки на классы, упомянутые в описателе метода, инкапсулированном типом метода, разрешаются (§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.<init>:(A*)void
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.6. Разрешение спецификатора места вызова

Для разрешения неразрешенной символической ссылки на спецификатор места вызова требуется три шага:

  • Спецификатор места вызова предоставляет символическую ссылку на обработчик метода, который должен служить методом инициализации для динамического места вызова. Обработчик метода разрешается (§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 объекты.

Результатом разрешения спецификатора места вызова является кортеж, состоящий из:

  • ссылка на экземпляр reference java.lang.invoke.MethodHandle,

  • ссылка на экземпляр reference java.lang.invoke.MethodType,

  • ссылки на экземпляры reference Class, java.lang.invoke.MethodHandle, java.lang.invoke.MethodType и String.

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

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

Класс или интерфейс 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); это не входит в контроль доступа во время связывания.

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

Инстансный метод 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.

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

Инициализация класса или интерфейса заключается в выполнении его метода инициализации класса или интерфейса (§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 затем выглядит следующим образом:

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

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

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

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

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

  6. В противном случае, зафиксировать факт, что инициализация объекта Class для C выполняется текущим потоком, и освободить LC. Затем инициализировать каждое final static поле C с постоянным значением в его атрибуте ConstantValue (§4.7.2), в порядке появления полей в структуре ClassFile.

  7. Далее, если C является классом, а не интерфейсом, и его суперкласс SC еще не инициализирован, рекурсивно выполнить всю эту процедуру для SC. При необходимости сначала проверить и подготовить SC.

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

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

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

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

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

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

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

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

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

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

END_OF_DOCUMENT_MARKER

5.7. Выход виртуальной машины 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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API