Загрузка, компоновка и инициализация в JVM

Виртуальная машина Java (JVM) выполняет за кулисами большую работу, выполняя двоичный файл классов, переданный ей для исполнения. Это не так просто, потому что это похоже на вызов JVM с помощью команды java с файлом класса, переданным ему в качестве аргумента командной строки. Файл класса проходит много этапов, но в этом посте мы в основном рассмотрим первые три этапа выполнения, называемые загрузкой, связыванием и процессом инициализации. Это огромная и сложная тема сама по себе. Поэтому здесь только кратко описаны ключевые аспекты, чтобы понять, что это такое.

Загрузка (loading), компоновка (связывание, linking) и инициализация (initialization) - это начальные процессы, которые JVM запускает, как только байт-код, называемый файлом класса, загружается в JVM для выполнения. Другие процессы, такие как создание экземпляров, сборка мусора и финализация, происходят на средних этапах жизненного цикла класса. И, наконец, процесс разгрузки происходит в конце жизненного цикла. JVM обеспечивает среду, в которой различные процессы протекают. Абсолютно не имеет значения, какой языковой компилятор используется для преобразования исходного кода в файл класса, если он соответствует стандарту, который понимает JVM.

Помимо Java, существует много известных языков JVM, таких как Clojure, Groovy, Scala, Jruby, Jython и так далее. Программа может быть написана на любом из этих языков и скомпилирована конкретным языковым компилятором. Скомпилированный целевой код создается для запуска в JVM.

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

Таким образом, помимо реализации JVM в Oracle и OpenJDK, на рынке доступны и другие активные реализации, такие как CACAO, jikes RVM, Maxine, JamVM и тому подобное.

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

Путешествие файла класса

Жизненный цикл файла классов Java: компилятор Java создает файл классов в качестве результата файла, переданного ему в качестве исходного кода. Файл класса, хотя это и двоичные данные, далеко не готов к выполнению на машине без виртуальной машины Java (JVM). Это означает, что файл класса полностью зависит от среды JVM для выполнения. JVM обеспечивает среду выполнения и понимает двоичную инструкцию, представленную в файле класса. Это JVM отвечает за взаимодействие с базовой платформой по инструкции файла исполняемого класса. JVM-посредник не только предоставляет площадку для файла классов, но и выступает в качестве посредника для обмена услугами и ресурсами. Поэтому, если мы сломаем процессы JVM, которые он предпримет, чтобы успешно выполнить файл класса, процессов будет много. Но, для начала, есть три процесса, которым JVM следует на начальных этапах импорта файла класса в свой домен. Эти три процесса называются загрузкой, связыванием и инициализацией.

Процесс загрузки

Согласно спецификации виртуальной машины Java 8, это процесс поиска двоичного представления класса или типа интерфейса с конкретным именем и создания класса или интерфейса из этого двоичного представления.

JVM предоставляет два типа загрузчиков классов. Один называется загрузчиком классов начальной загрузки (bootstrap class loader), а другой - определяемым пользователем загрузчиком классов (user-defined class loader). Загрузчик класса начальной загрузки жестко определен в JVM и загружает файлы классов в соответствии со спецификацией. Пользовательский загрузчик классов открыт для реализации конкретного поставщика и может настраивать загрузку классов с помощью экземпляра java.lang.Class. Обратите внимание, что (в документации по API Java) этот класс не имеет открытого конструктора. В результате объекты Class автоматически создаются JVM, и можно получить всю информацию о внутренней структуре данных класса через функции-члены этого класса. После загрузки класса JVM анализирует его в соответствии с внутренней структурой данных. Обычно загрузчик классов кэширует двоичное представление типа либо во время загрузки, либо заблаговременно или по отношению к группе классов. Если возникает какая-либо проблема, даже на начальных этапах времени загрузки, скажем, из-за неправильно сформированного класса, JVM не сразу сообщает о проблеме; вместо этого она ожидает, пока программа активно не обратится к классу, и сообщит об ошибке компоновщика. Если в течение всего цикла работы программы такая ссылка не указана, ошибка может сохраняться, но отчет не будет создаваться.

Следовательно, в двух словах, процесс загрузки в основном выполняет эти три функции:

  • Создать двоичный поток данных из файла класса
  • Разобрать двоичные данные в соответствии с внутренней структурой данных
  • Создать экземпляр java.lang.Class

Процесс связывания

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

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

Существует список исключений, определенных JVM, чтобы выдавать их при определенных обстоятельствах. В связи с этим стоит упомянуть о том, что существуют проверочные биты, и проверка происходит с самого начала, когда двоичные данные анализируются во внутренней структуре данных, и проверки в этом процессе гарантируют, что операция не завершится сбоем. Также выполняется проверка, чтобы убедиться, что структура двоичных данных соответствует ожидаемому формату. Загрузчик также проверяет, что класс является подклассом java.lang.Object, с единственным исключением, являющимся самим классом Object. Это часто требует рекурсивной загрузки иерархии суперкласса. Таким образом, многочисленные проверки выполняются на нескольких этапах, но обычно считается, что официальная проверка начинается со связывания.

После завершения проверки JVM выделяет память для переменных класса и инициализирует их значениями по умолчанию в соответствии с типом переменной. Фактическая инициализация (с пользовательскими значениями инициализации), однако, не происходит до следующего этапа инициализации. Этот процесс называется подготовкой (Preparation).

Наконец, на необязательном этапе разрешения (Resolution) JVM находит классы, интерфейсы, поля и методы, на которые есть ссылки в пуле констант (таблица символов), и определяет конкретные значения по их символьной ссылке. Разрешение символьных ссылок Java, опять же, открыто для конкретной реализации. JVM может решить разрешить символические ссылки в классе или интерфейсе, когда он используется, или разрешить их в процессе проверки (верификации). Вкратце, проверка (верификация) убеждается, что двоичное представление класса является структурно корректным. И это может гарантировать, что ему, возможно, придется загружать дополнительные классы (возможно) без какой-либо необходимости проверять эти классы (если эти классы являются частью библиотеки API Java).

Следовательно, в двух словах, процесс связывания включает три функции:

  • Верификация (Verification)
  • Подготовка (Preparation)
  • Разрешение (Resolution) (необязательно)

Процесс инициализации

Согласно спецификации виртуальной машины Java 8, инициализация класса или интерфейса состоит из выполнения его метода инициализации класса или интерфейса.

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

Таким образом, чтобы подвести итог, процесс инициализации включает в себя следующие две функции:

  • Инициализировать переменные класса с помощью подпрограммы, указанной программистом.
  • Инициализировать его суперклассы, если они еще не инициализированы.

Это очень краткий обзор процесса загрузки, компоновки и инициализации в JVM. Есть много более тонких особенностей, вовлеченных в каждый из описанных этапов, но эти тонкости были опущены, чтобы сделать пост простым и лаконичным.


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


Комментарии

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

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

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

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