на чем написана jvm

Что такое JVM? Знакомство с виртуальной машиной Java

Java virtual machine (JVM) — это программа, которая разработана для выполнения и запуска других программ на основе Java. В основе JVM лежит простая и гениальная идея, которая всегда останется одним из величайших примеров программирования в стиле кунг-фу. JVM может также использоваться для выполнения программ, написанных на других языках программирования. Подробно рассказываем, как работает JVM, для чего используется эта технология и почему она является одним из главных компонентов в платформе Java. Материал основан на статье Java-разработчика Matthew Tyson «What is the JVM? Introducing the Java Virtual Machine».

Для чего используется Java virtual machine

JVM имеет две основные функции:

Во время выхода первой версии Java в 1995 году все программы писались для конкретной операционной системы, а памятью управлял разработчик программного обеспечения. Поэтому появление JVM стало революцией на рынке.

Существует два основных определения JVM — техническое и повседневное:

Когда разработчики говорят о JVM, обычно имеется в виду процесс, который выполняется на нашем устройстве, особенно на сервере — он управляет и контролирует использование ресурсов Java-приложения.

Кто разрабатывает и обслуживает JVM?

На сегодняшний день JVM массово используется и развивается в разных проектах — как коммерческих, так и Open Sourse. Например, существует проект OpenJDK, который представляет собой полностью совместимый Java Development Kit, состоящий исключительно из свободного и открытого исходного кода. При этом, несмотря на открытость кода этого проекта, его разработкой практически полностью занимается корпорация Oracle.

Сборка мусора

В Java памятью управляет JVM с помощью процесса, который называется сборкой мусора — он непрерывно определяет и удаляет неиспользуемую память в Java-приложениях. Сборка мусора происходит внутри работающей JVM.

В начале существования Java подвергалась серьезной критике за то, что не была «Close to the metal» как C++, поэтому не была такой быстрой. Особенно спорным критики называли процесс сборки мусора. С тех пор были предложены и использованы различные алгоритмы и подходы, которые значительно улучшили и оптимизировали сборку мусора.

Три главные части JVM

JVM состоит из трех основных частей: спецификация, реализация и экземпляр. Рассмотрим каждую из них.

Спецификация JVM

Первая часть JVM — спецификация, которая до конца не определяет все детали реализации виртуальной машины. Это значит, что остается максимальная свобода творчества для разработчика, который работает с ней. Чтобы правильно реализовать виртуальную машину Java, вам нужно всего лишь уметь читать class-файлы и правильно выполнять указанные в них операции.

И так, все, что должна делать JVM — правильно запускать Java-программы. Это может показаться достаточно простым процессом, однако это очень масштабная задача, учитывая мощность и гибкость языка Java.

Реализация JVM

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

Экземпляр JVM

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

Чаще всего, когда разработчики говорят о JVM, они имеют ввиду экземпляр JVM, который работает в среде разработки. Вы можете сказать: «Привет, сколько памяти использует JVM на этом сервере?» или «Я не могу поверить, что сделал зацикленный вызов, а переполнение стека сломало мою JVM. А ведь это просто ошибка новичка!»

Загрузка и выполнение class-файлов в JVM

Мы говорили о роли JVM в запуске Java-приложений, но как виртуальная машина выполняет свою функцию? Для запуска Java-приложений JVM зависит от загрузчика классов и механизма выполнения Java.

Читайте также:  Уши оттопыренные что значит

Загрузчик классов в JVM

Загрузчик классов Java является частью JVM — он загружает классы в память и делает их доступными для выполнения. Загрузчик классов использует технику ленивой загрузки (lazy-loading) и кэширование, чтобы сделать загрузку классов максимально эффективной. При этом использование таких методов считается достаточно простым процессом.

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

Механизм выполнения в JVM

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

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

Управление системными ресурсами

Системные ресурсы могут быть разделены на две больших категории: память и все остальное.

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

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

Эволюция JVM: прошлое, настоящее, будущее

В 1995 году разработчики JVM представили две революционные концепции, которые с тех пор стали стандартом в разработке: «Написал один раз, запускай везде» и автоматическое управление памятью. В то время совместимость софта была смелой концепцией, но сейчас это стало нормой. Точно так же, как современное поколение живет с автоматической сборкой мусора.

Можно сказать, что если Джеймс Гослинг и Брендан Эйх изобрели современное программирование, то тысячи других разработчиков усовершенствовали и развили их идеи в последующие десятилетия. Изначально виртуальная машина Java предназначалась только для Java, но сегодня она эволюционировала до поддержки многих языков программирования, включая Scala, Groovy и Kotlin.

Изучайте Java на Хекслете Вступайте в профессию и изучайте один из самых востребованных в энтерпрайзе языков программирования.

Источник

Как написать (игрушечную) JVM

Статья про KVM оказалась интересной для читателей, поэтому сегодня публикуем новый перевод статьи Serge Zaitsev: как работает Java Virtual Machine под капотом.

Нравится нам это или нет, но Java — один из наиболее распространенных и широко используемых языков программирования. Однако не каждый Java-разработчик настолько любопытен, чтобы заглянуть под капот и посмотреть, как устроена JVM.

Я попытаюсь написать игрушечную (и неполную) JVM, чтобы показать основные принципы ее работы. Надеюсь, эта статья вызовет у вас интерес и вдохновит на дальнейшее изучение JVM.

Наша скромная цель

Мы компилируем наш класс с javac Add.java, и в результате получается Add.class. Этот class файл является бинарным файлом, который JVM может выполнять. Всё, что нам осталось сделать, — создать такую JVM, которая бы выполняла его корректно.

Читайте также:  можжевельник ржавчина что делать

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

Хотя мы еще не видим здесь четкой структуры, нам нужно найти способ ее разобрать: что значит ()V и (II)I, и почему файл начинается с «cafe babe»?

Вероятно, вы знакомы с другим способом выгрузить class файлы, часто он оказывается более полезным:

public static int add(int, int);
Code:
0: iload_0
1: iload_1
2: iadd
3: ireturn
>

Теперь мы видим наш класс, его конструктор и метод. И конструктор, и метод содержат несколько инструкций. Становится более-менее ясно, что делает наш метод add(): он загружает два аргумента (iload_0 и iload_1), суммирует их и возвращает результат. JVM — это стековая машина, поэтому в ней нет регистров, все аргументы инструкций хранятся во внутреннем стеке, и результаты также помещаются в стек.

Class loader

Как нам добиться того же результата, который показала программа javap? Как разобрать class файл?

Если обратиться к спецификации JVM, мы узнаем о структуре файлов формата class. Он всегда начинается с 4-байтовой подписи (CAFEBABE), затем 2 + 2 байта для версии. Звучит просто!

Поскольку нам пришлось бы читать байты, short, int и байтовые последовательности из бинарного файла, начнем реализацию нашего загрузчика следующим образом:

Затем спецификация сообщает нам, что необходимо распарсить пул констант. Что это? Так называется специальная часть class файла, которая содержит константы, необходимые для запуска класса. Все строки, числовые константы и ссылки хранятся там, и каждая имеет уникальный индекс uint16 (таким образом, класс может иметь до 64К констант).

В пуле есть несколько типов констант, каждый из которых содержит свой набор значений. Нам могут быть интересны:

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

Сейчас мы сильно упрощаем себе задачу, но в реальной JVM нам пришлось бы обрабатывать типы констант long и double единообразно, вставляя дополнительный неиспользуемый постоянный элемент, как сообщает нам спецификация JVM (поскольку постоянные элементы считаются 32-битными).

Чтобы упростить получение строковых литералов по индексам, реализуем метод Resolve(index uint16) string:

Теперь нам нужно добавить похожие помощники для анализа списка интерфейсов, полей и методов классов и их атрибутов:

И поля, и методы представлены как Fields, это очень удобно, позволяет сэкономить время. Наконец, мы можем собрать всё это вместе и полноценно распарсить наш класс:

Теперь, если мы посмотрим на полученную информацию о классе, мы увидим, что у него нет полей и два метода — :()V и add:(II)I. Что за римские числа со скобками? Это дескрипторы. Они определяют, какие типы аргументов принимает метод и что он возвращает. В этом случае (синтетический метод, используемый для инициализации объектов при их создании) не принимает аргументов и ничего не возвращает (V=void), в то время как метод «add» принимает два типа данных int (I=int32) и возвращает целое число.

Байт-код

Если присмотреться внимательно, мы поймем, что каждый метод в нашем разобранном классе имеет один атрибут с именем «Code». Этот атрибут содержит часть байтов в качестве полезной нагрузки. Байты:

:
[0 1 0 1 0 0 0 5 42 183 0 1 177 0 0 0 1 0 7 0 0 0 6 0 1 0 0 0 1]
add:
[0 2 0 2 0 0 0 4 26 27 96 172 0 0 0 1 0 7 0 0 0 6 0 1 0 0 0 3]

В спецификации, на этот раз в разделе bytecode, мы прочтем, что атрибут «Code» начинается со значения maxstack (2 байта), затем maxlocals (2 байта), длина кода (4 байта) и затем фактический код. Итак, наши атрибуты можно читать так:

Читайте также:  можно ли стричь кусты летом

: maxstack: 1, maxlocals: 1, code: [42 183 0 1 177]
add: maxstack: 2, maxlocals: 2, code: [26 27 96 172]

Да, у нас есть только 4 и 5 байтов кода в каждом методе. Что означают эти байты?

Как я уже сказал, JVM — это стековая машина. Каждая инструкция кодируется как один байт, за которым могут следовать дополнительные аргументы. Заглянув в спецификацию, мы увидим, что метод «add» имеет следующие инструкции:

26 = iload_0
27 = iload_1
96 = iadd
172 = ireturn

Точно такие же, как мы видели в выводе javap в начале! Но как это сделать?

Фреймы JVM

Когда метод выполняется внутри JVM, у него есть собственный стек для временных операндов, свои локальные переменные и фрагмент кода для выполнения. Все эти параметры хранятся в едином фрейме выполнения. Кроме того, фреймы содержат указатель текущей инструкции (насколько далеко мы продвинулись при выполнении байт-кода) и указатель на класс, в котором содержится метод. Последний нужен для доступа к пулу констант класса, а также к другим деталям.

Давайте создадим метод, который создает фрейм для конкретного метода, вызванного с заданными аргументами. Я буду использовать здесь тип interface<> в качестве типа Value, хотя использование правильного объединения типов (union types), конечно, было бы более безопасным.

Итак, мы получили Frame с инициализированными локальными переменными, пустым стеком и предварительно загруженным байт-кодом. Пришло время выполнить байт-код:

Наконец, мы можем собрать все вместе и запустить, вызвав наш метод add():

Итак, всё работает. Да, это очень слабая JVM на минималках, но все же она делает то, что и должна JVM: загружает байт-код и интерпретирует его (конечно, настоящая JVM делает гораздо большее).

Чего не хватает?

Оставшихся 200 инструкций, среды выполнения, системы типов ООП и еще нескольких вещей.

Существует 11 групп инструкций, большинство из которых банальны:

Для реализации References необходимо подумать об объектной модели: как вы хотите хранить объекты и их классы, как представлять наследование, где хранить поля экземпляров и поля классов. Кроме того, здесь вы должны быть осторожны с диспетчеризацией методов — существует несколько инструкций «invoke», и они ведут себя по-разному:

Наконец, ваша JVM останется бесполезной, если нет runtime классов. Без java/lang/Object вы вряд ли даже увидите, как работает new инструкция при создании новых объектов. Ваша среда выполнения может предоставлять некоторые общие классы JRE из пакетов java.lang, java.io и java.util, или это может быть что-то более специфичное для домена. Скорее всего, некоторые методы в классах должны быть реализованы изначально, а не на Java. Это поднимет вопрос о том, как найти и выполнить такие методы, и станет еще одним крайним случаем для вашей JVM.

Другими словами, создать правильную JVM не так уж и просто, однако разобраться, как именно она работает, — не сложно.

У меня, например, на это ушли всего одни летние выходные. Моей JVM еще предстоит долгий путь, но структура выглядит более или менее ясной: https://github.com/zserge/tojvm (замечания всегда приветствуются!)

Фактические фрагменты кода из этой статьи еще меньше и доступны здесь как gist.

Если появилось желание изучить вопрос лучше, вы можете подумать об использовании небольших JVM:

Надеюсь, вам понравилась моя статья. Вы можете следить за моей работой через Github или Twitter, а также подписываться через rss.

Источник

Строительный портал