Спецификация Java 11: 12.4.2. Подробная процедура инициализации

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

Процедура предполагает, что объект Class уже был проверен и подготовлен, и что объект Class содержит состояние, которое указывает на одну из четырех ситуаций:

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

Для каждого класса или интерфейса C существует уникальная блокировка инициализации LC. Отображение от C к LC оставлено на усмотрение реализации виртуальной машины Java. Затем процедура инициализации 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.
    Затем инициализируйте статические поля C, которые являются постоянными переменными (§4.12.4, §8.3.2, §9.3.1).
  7. Затем, если C - это класс, а не интерфейс, пусть SC будет его суперклассом, а SI1, ..., SIn - всеми суперинтерфейсами C, которые объявляют по крайней мере один метод по умолчанию. Порядок суперинтерфейсов задается рекурсивным перечислением по иерархии суперинтерфейсов каждого интерфейса, непосредственно реализованного C (в порядке слева направо в предложении C implements). Для каждого интерфейса, который я напрямую реализовал с помощью C, перечисление повторяется на суперинтерфейсах I (в порядке слева направо в предложении I extends) перед возвратом I.
    Для каждого S в списке [SC, SI1, ..., SIn], если S еще не был инициализирован, рекурсивно выполните всю эту процедуру для S. При необходимости сначала проверьте и подготовьте S.
    Если инициализация S завершается внезапно из-за сгенерированного исключения, тогда получите LC, пометьте объект Class для C как ошибочный, уведомите все ожидающие потоки, освободите LC и завершите внезапно, выбрасывая то же исключение, которое возникло в результате инициализации S.
  8. Затем определите, разрешены ли утверждения (§14.10) для C, запросив его определяющий загрузчик классов.
  9. Затем выполните либо инициализаторы переменных класса и статические инициализаторы класса, либо инициализаторы полей интерфейса в текстовом порядке, как если бы они были одним блоком.
  10. Если выполнение инициализаторов завершается нормально, тогда получите LC, пометьте объект Class для C как полностью инициализированный, уведомите все ожидающие потоки, освободите LC и завершите эту процедуру обычным образом.
  11. В противном случае инициализаторы должны завершиться внезапно, вызвав какое-то исключение E. Если класс E не является Error или одним из его подклассов, тогда создайте новый экземпляр класса ExceptionInInitializerError с E в качестве аргумента и используйте этот объект вместо E на следующем шаге. Если новый экземпляр ExceptionInInitializerError не может быть создан из-за возникновения OutOfMemoryError, вместо этого используйте объект OutOfMemoryError вместо E на следующем шаге.
  12. Получите LC, пометьте объект Class для C как ошибочный, уведомите все ожидающие потоки, освободите LC и резко завершите эту процедуру, указав причину E или ее замену, как определено на предыдущем шаге.

Реализация может оптимизировать эту процедуру, исключая получение блокировки на шаге 1 (и освобождение на шаге 4/5), когда она может определить, что инициализация класса уже завершена, при условии, что с точки зрения модели памяти все происходит: события, которые происходят-до, которые существовали бы, если бы блокировка была получена, все еще существуют при выполнении оптимизации.

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

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


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки