Где начинается этап разрешения загрузки Java-класса?

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

  • Создание/загрузка: загрузчик классов обнаруживает поток байтов, представляющий класс, либо файл, либо сетевой поток, либо все, что реализует загрузчик классов для извлечения. Если класс не найден, генерируется ClassNotFoundException. На этом этапе уже существует некоторая базовая проверка, когда возникает ClassFormatError, если массив байтов не представляет класс Java (например, магическое число отсутствует) или UnsupportedClassVersionError, если версия класса не поддерживается с помощью исполняемого экземпляра JVM.

  • Связь. Класс подключен к JVM. Если что-то пойдет не так, подклассом LinkageError выдается. Связывание состоит из трех подэтапов:

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

    • Подготовка. JVM выделяет память для всех статических полей и может создать шаблон экземпляра для ускорения создания экземпляра. Создаются таблицы виртуальных методов. На этом этапе не будут выбраны конкретные ошибки загрузки классов. (Может быть брошен An OutOfMemoryError.)

    • Разрешение. Все символические ссылки, которые теперь были загружены в область метода в виде пула констант времени выполнения, разрешены для реальных типов, загружаемых этой JVM. Если символическая ссылка может быть разрешена, но приводит к конфликту определений, бросается IncompatibleClassChangeError. Если ссылочный класс не найден, генерируется NoClassDefFoundError, который в основном обертывает ClassNotFoundException, который был брошен загрузчиком класса, пытающимся загрузить этот ссылочный класс. Если ссылочный класс ссылается на себя, генерируется ClassCircularityError. Разрешение может быть выполнено в одном из двух вариантов, который соответствует разработчикам JVM

      • Eager: все символические ссылки на другие поля, методы или классы разрешаются прямо сейчас.

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

  • Инициализация: инициализаторы класса static, которые определены в классе как Java-код, запускаются. Если исключение вызвано таким инициализатором, это исключение повторно заверяется в ExceptionInInitializerError.

Что меня озадачивает фаза разрешения вышеупомянутого механизма загрузки класса. Почему разрешение определяется как явный шаг в связывании, который происходит непосредственно после подготовки? Уже в описании фазы загрузки класса указано, что

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

Символьные ссылки также не разрешаются во время проверки, так как проверка описана:

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

Я всегда помню эту фотографию

Java class loading overview

Источник: http://www.programcreek.com

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

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

Я подозреваю, что это изображение разрешения является выделенным шагом, может быть, просто наследием с момента, когда разрешение никогда не проводилось лениво, но имело место, где были устранены все остальные символические ссылки.

Что я хочу знать. Должно ли быть разрешено в JVM на сегодняшний день, как я это описал? Или я ошибаюсь в этом, и разрешение все еще можно понять как выделенный шаг в фиксированной строке времени, как показывает изображение?

Ответы

Ответ 1

Ваша фотография показывает разрешение, как всегда появляется после подготовки, но это не сработает. Прямые суперклассы необходимы для подготовки, так как вам нужны знания о полях экземпляров суперклассов для определения макета памяти экземпляра объекта для определенного класса. Кроме того, статические инициализаторы класса и его суперклассы должны были быть выполнены до того, как класс может быть использован, т.е. Перед созданием экземпляров или перед вызовом статических методов.

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

Когда вы смотрите на начало главы 5.4.3. Разрешение, четко указано:

Команды виртуальной машины Java anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield и putstatic делают символические ссылки на постоянный пул времени выполнения, Выполнение любой из этих инструкций требует разрешения ее символической ссылки.

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

Ответ 2

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

Попробуйте изучить исходный код OpenJDK, и вы можете найти что-то интересное.