Виртуальная машина Java ( JVM ) — это виртуальная машина , которая позволяет компьютеру запускать программы Java , а также программы, написанные на других языках , которые также компилируются в байт-код Java . JVM детализируется спецификацией , которая формально описывает, что требуется в реализации JVM. Наличие спецификации обеспечивает совместимость программ Java в различных реализациях, так что авторам программ, использующим Java Development Kit (JDK), не нужно беспокоиться об особенностях базовой аппаратной платформы.
Эталонная реализация JVM разработана проектом OpenJDK как открытый исходный код и включает JIT - компилятор HotSpot . Коммерчески поддерживаемые выпуски Java, доступные от Oracle, основаны на среде выполнения OpenJDK. Eclipse OpenJ9 — еще одна JVM с открытым исходным кодом для OpenJDK.
Виртуальная машина Java — это абстрактный (виртуальный) компьютер, определенный спецификацией. Он является частью среды выполнения Java. Используемый алгоритм сборки мусора и любая внутренняя оптимизация инструкций виртуальной машины Java (их перевод в машинный код ) не указаны. Основная причина этого упущения — не ограничивать без необходимости разработчиков. Любое приложение Java может быть запущено только внутри некоторой конкретной реализации абстрактной спецификации виртуальной машины Java. [2]
Начиная с Java Platform, Standard Edition (J2SE) 5.0, изменения в спецификации JVM были разработаны в рамках Java Community Process как JSR 924. [3] С 2006 года [обновлять]изменения в спецификации для поддержки изменений, предложенных для формата файла класса (JSR 202) [4] , вносятся в качестве релиза обслуживания JSR 924. Спецификация для JVM была опубликована в виде синей книги [5] , в предисловии к которой говорится:
Мы предполагаем, что эта спецификация должна в достаточной степени документировать Java Virtual Machine, чтобы сделать возможными совместимые реализации для чистых помещений. Oracle предоставляет тесты, которые проверяют правильность работы реализаций Java Virtual Machine.
Одна из JVM от Oracle называется HotSpot; другая, унаследованная от BEA Systems , называется JRockit . Oracle владеет торговой маркой Java и может разрешить ее использование для сертификации комплектов реализаций как полностью совместимых со спецификацией Oracle.
Одной из организационных единиц байт-кода JVM является класс . Реализация загрузчика классов должна быть способна распознавать и загружать все, что соответствует формату файла класса Java . Любая реализация может распознавать другие двоичные формы, помимо файлов классов , но она должна распознавать файлы классов .
Загрузчик классов выполняет три основных действия в строгом порядке:
В целом существует три типа загрузчиков классов: загрузчик классов начальной загрузки, загрузчик классов расширения и загрузчик классов системы/приложения.
Каждая реализация виртуальной машины Java должна иметь загрузчик классов bootstrap, способный загружать доверенные классы, а также загрузчик классов расширения или загрузчик классов приложения. Спецификация виртуальной машины Java не определяет, как загрузчик классов должен находить классы.
JVM работает с определенными типами данных, как указано в спецификациях виртуальной машины Java. Типы данных можно разделить [6] на примитивные типы ( целые , с плавающей точкой, длинные и т. д.) и ссылочные типы. Ранние JVM были только 32-битными машинами. long
и double
типы, которые являются 64-битными , поддерживаются изначально, но занимают две единицы хранения в локальных переменных фрейма или стеке операндов, поскольку каждая единица составляет 32 бита. boolean
, byte
, short
, и char
все типы являются знаковыми (за исключением char
расширенного нуля ) и обрабатываются как 32-битные целые числа, так же как и int
типы. Меньшие типы имеют только несколько специфичных для типа инструкций для загрузки, хранения и преобразования типов. boolean
обрабатывается как 8-битные byte
значения, где 0 представляет false
, а 1 представляет true
. (Хотя boolean
он рассматривается как тип с тех пор, как Спецификация виртуальной машины Java, второе изданиеboolean
прояснила эту проблему, в скомпилированном и исполняемом коде между a и a мало различий, byte
за исключением искажения имен в сигнатурах методов и типа булевых массивов. boolean
s в сигнатурах методов искажаются как , Z
а byte
s искажаются как B
. Булевы массивы содержат тип boolean[]
, но используют 8 бит на элемент, и JVM не имеет встроенной возможности упаковывать булевы значения в битовый массив , поэтому за исключением типа они выполняют и ведут себя так же, как byte
массивы. Во всех других случаях boolean
тип фактически неизвестен JVM, поскольку все инструкции для работы с булевыми значениями также используются для работы с byte
s.) Однако более новые выпуски JVM (OpenJDK HotSpot JVM) поддерживают 64-битную версию, поэтому вы можете использовать как 32-битную, так и 64-битную JVM в 64-битной ОС. Основным преимуществом запуска Java в 64-битной среде является большее адресное пространство. Это позволяет значительно увеличить размер кучи Java и максимальное количество потоков Java, что необходимо для определенных видов больших приложений. Однако при использовании 64-разрядной JVM наблюдается снижение производительности по сравнению с 32-разрядной JVM.
JVM имеет кучу со сборщиком мусора для хранения объектов и массивов. Код, константы и другие данные классов хранятся в «области методов». Область методов логически является частью кучи, но реализации могут обрабатывать область методов отдельно от кучи и, например, не собирать ее. Каждый поток JVM также имеет свой собственный стек вызовов (называемый для ясности «стеком виртуальной машины Java»), в котором хранятся кадры . Новый кадр создается каждый раз при вызове метода, и кадр уничтожается при выходе из этого метода.
Каждый фрейм предоставляет «стек операндов» и массив «локальных переменных». Стек операндов используется для операндов для запуска вычислений и для получения возвращаемого значения вызванного метода, в то время как локальные переменные служат той же цели, что и регистры , и также используются для передачи аргументов метода. Таким образом, JVM является как стековой машиной , так и регистровой машиной . На практике HotSpot устраняет любой стек, кроме собственного потока/стека вызовов, даже при работе в режиме интерпретации, поскольку его интерпретатор шаблонов технически функционирует как компилятор.
JVM имеет инструкции для следующих групп задач:
Целью является двоичная совместимость. Каждой конкретной операционной системе хоста нужна своя реализация JVM и среды выполнения. Эти JVM интерпретируют байт-код семантически одинаково, но фактическая реализация может отличаться. Более сложным, чем просто эмуляция байт-кода, является совместимая и эффективная реализация API ядра Java , которая должна быть сопоставлена с каждой операционной системой хоста.
Эти инструкции работают на основе набора общихабстрактные типы данных , а не собственные типы данных какой-либо конкретной архитектуры набора инструкций .
Язык JVM — это любой язык с функциональностью, которая может быть выражена в терминах допустимого файла класса, который может быть размещен виртуальной машиной Java. Файл класса содержит инструкции виртуальной машины Java ( байт-код Java ) и таблицу символов, а также другую вспомогательную информацию. Формат файла класса — это независимый от оборудования и операционной системы двоичный формат, используемый для представления скомпилированных классов и интерфейсов. [7]
Существует несколько языков JVM, как старые языки, портированные на JVM, так и совершенно новые языки. JRuby и Jython , пожалуй, самые известные порты существующих языков, то есть Ruby и Python соответственно. Из новых языков, которые были созданы с нуля для компиляции в байт-код Java, Clojure , Groovy , Scala и Kotlin, возможно, самые популярные. Примечательной особенностью языков JVM является то, что они совместимы друг с другом , так что, например, библиотеки Scala можно использовать с программами Java и наоборот. [8]
Java 7 JVM реализует JSR 292: Supporting Dynamically Typed Languages [9] на платформе Java, новую функцию, которая поддерживает динамически типизированные языки в JVM. Эта функция разработана в рамках проекта Da Vinci Machine , миссия которого заключается в расширении JVM таким образом, чтобы она поддерживала языки, отличные от Java. [10] [11]
Основная философия Java заключается в том, что она изначально безопасна с точки зрения того, что никакая пользовательская программа не может вызвать сбой хост-машины или иным образом ненадлежащим образом вмешаться в другие операции на хост-машине, и что можно защитить определенные методы и структуры данных, принадлежащие доверенному коду, от доступа или повреждения ненадежным кодом, выполняемым в той же JVM. Кроме того, не допускаются распространенные ошибки программиста, которые часто приводили к повреждению данных или непредсказуемому поведению, такому как доступ за пределы конца массива или использование неинициализированного указателя. Несколько функций Java объединяются для обеспечения этой безопасности, включая модель классов, кучу со сборкой мусора и верификатор.
JVM проверяет весь байт-код перед его выполнением. Эта проверка в основном состоит из трех типов проверок:
Первые две из этих проверок происходят в основном на этапе проверки, который происходит, когда класс загружается и становится пригодным для использования. Третья в основном выполняется динамически, когда элементы данных или методы класса впервые получают доступ к другому классу.
Верификатор допускает только некоторые последовательности байт-кода в допустимых программах, например, инструкция перехода (ветвления) может быть нацелена только на инструкцию в том же методе . Кроме того, верификатор гарантирует, что любая данная инструкция работает в фиксированном месте стека, [12] позволяя JIT-компилятору преобразовывать доступ к стеку в фиксированный доступ к регистрам. Из-за этого тот факт, что JVM является стековой архитектурой, не подразумевает снижения скорости эмуляции на архитектурах на основе регистров при использовании JIT-компилятора. Перед лицом архитектуры JVM с проверкой кода для JIT-компилятора не имеет значения, получает ли он именованные мнимые регистры или мнимые позиции стека, которые должны быть выделены регистрам целевой архитектуры. Фактически, проверка кода отличает JVM от классической стековой архитектуры, эффективная эмуляция которой с помощью JIT-компилятора сложнее и обычно выполняется более медленным интерпретатором. Кроме того, интерпретатор, используемый JVM по умолчанию, представляет собой особый тип, известный как шаблонный интерпретатор, который транслирует байт-код непосредственно в собственный машинный язык на основе регистров, а не эмулирует стек, как типичный интерпретатор. [13] Во многих аспектах интерпретатор HotSpot можно считать скорее JIT-компилятором, чем настоящим интерпретатором, то есть архитектура стека, на которую нацелен байт-код, на самом деле не используется в реализации, а является просто спецификацией для промежуточного представления, которое вполне может быть реализовано в архитектуре на основе регистров. Другим примером архитектуры стека, являющейся просто спецификацией и реализованной в виртуальной машине на основе регистров, является Common Language Runtime . [14]
Первоначальная спецификация для байт-кода верификатора использовала естественный язык, который был неполным или неверным в некоторых отношениях. Было предпринято несколько попыток определить JVM как формальную систему. Сделав это, можно будет более тщательно проанализировать безопасность текущих реализаций JVM и предотвратить потенциальные уязвимости безопасности. Также можно будет оптимизировать JVM, пропуская ненужные проверки безопасности, если будет доказано, что запущенное приложение безопасно. [15]
Архитектура виртуальной машины позволяет осуществлять очень детальный контроль над действиями, которые коду в машине разрешено выполнять. Она предполагает, что код «семантически» корректен, то есть успешно прошел (формальный) процесс проверки байт-кода, материализованный инструментом, возможно, за пределами виртуальной машины. Это разработано для обеспечения безопасного выполнения ненадежного кода из удаленных источников, модели, используемой апплетами Java , и других безопасных загрузок кода. После проверки байт-кода загруженный код запускается в ограниченной « песочнице », которая предназначена для защиты пользователя от некорректного или вредоносного кода. В качестве дополнения к процессу проверки байт-кода издатели могут приобрести сертификат, с помощью которого можно подписывать апплеты как безопасные, что дает им разрешение просить пользователя выйти из песочницы и получить доступ к локальной файловой системе, буферу обмена , выполнять внешние части программного обеспечения или сети.
Формальное доказательство верификаторов байт-кода было сделано индустрией Javacard (Формальная разработка встроенного верификатора для байт-кода Java Card [16] )
Для каждой аппаратной архитектуры требуется свой интерпретатор байт-кода Java . Когда на компьютере есть интерпретатор байт-кода Java, он может запустить любую программу байт-кода Java, и эта же программа может быть запущена на любом компьютере, имеющем такой интерпретатор.
Когда байт-код Java выполняется интерпретатором, выполнение всегда будет медленнее, чем выполнение той же программы, скомпилированной в машинный язык. Эта проблема смягчается компиляторами just-in-time (JIT) для выполнения байт-кода Java. JIT-компилятор может транслировать байт-код Java в машинный язык во время выполнения программы. Затем транслированные части программы могут быть выполнены гораздо быстрее, чем они могли бы быть интерпретированы. Этот метод применяется к тем частям программы, которые часто выполняются. Таким образом, JIT-компилятор может значительно ускорить общее время выполнения.
Нет никакой необходимой связи между языком программирования Java и байт-кодом Java. Программа, написанная на Java, может быть скомпилирована непосредственно в машинный язык реального компьютера, а программы, написанные на других языках, кроме Java, могут быть скомпилированы в байт-код Java.
Байт-код Java призван быть платформенно-независимым и безопасным. [17] Некоторые реализации JVM не включают интерпретатор, а состоят только из компилятора JIT. [18]
В начале жизненного цикла платформы Java JVM позиционировалась как веб-технология для создания Rich Web Applications . По состоянию на 2018 год [обновлять]большинство веб-браузеров и операционных систем, включающих веб-браузеры, не поставляются с подключаемым модулем Java и не допускают стороннюю загрузку любого не- Flash -плагина. Плагин браузера Java был объявлен устаревшим в JDK 9. [19]
Плагин браузера NPAPI Java был разработан, чтобы позволить JVM выполнять так называемые Java-апплеты, встроенные в HTML-страницы. Для браузеров с установленным плагином апплету разрешено рисовать в прямоугольной области на странице, назначенной ему. Поскольку плагин включает JVM, апплеты Java не ограничены языком программирования Java; любой язык, ориентированный на JVM, может работать в плагине. Ограниченный набор API позволяет апплетам получать доступ к микрофону пользователя или 3D-ускорению, хотя апплеты не могут изменять страницу за пределами ее прямоугольной области. Adobe Flash Player , основная конкурирующая технология, работает таким же образом в этом отношении.
[обновлять]По данным W3Techs, по состоянию на июнь 2015 года использование Java-апплетов и Silverlight снизилось до 0,1% для всех веб-сайтов, тогда как Flash снизился до 10,8%. [20]
С мая 2016 года JavaPoly позволяет пользователям импортировать немодифицированные библиотеки Java и вызывать их напрямую из JavaScript. JavaPoly позволяет веб-сайтам использовать немодифицированные библиотеки Java, даже если на компьютере пользователя не установлена Java. [21]
С постоянным улучшением скорости выполнения JavaScript, в сочетании с возросшим использованием мобильных устройств, веб-браузеры которых не реализуют поддержку плагинов, предпринимаются попытки нацелиться на этих пользователей посредством транспиляции в JavaScript. Можно либо транспилировать исходный код, либо байт-код JVM в JavaScript.
Компиляция байт-кода JVM, который является универсальным для языков JVM, позволяет строить на основе существующего компилятора языка в байт-код. Основными транспиляторами байт-кода JVM в JavaScript являются TeaVM, [22] компилятор, содержащийся в Dragome Web SDK, [23] Bck2Brwsr, [24] и j2js-compiler. [25]
Ведущие транспиляторы из языков JVM в JavaScript включают транспилятор Java-to-JavaScript, содержащийся в Google Web Toolkit , Clojurescript (Clojure), GrooScript (Apache Groovy), Scala.js (Scala) и другие. [26]