Что такое 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 на Хекслете Вступайте в профессию и изучайте один из самых востребованных в энтерпрайзе языков программирования.
Виртуальная машина и первая команда
Амиго нервничал. Его мысли скакали и путались. Глаза подергивались и холодели от одной мысли о вчерашнем вечере. Эти странные существа, вчерашние знакомые, кое-что хотят от него. Нечто настолько непонятное и невообразимое, что даже он, считающий себя самым умным и смелым среди сверстников, начинает нервно жевать перфокарты от одной мысли об этом.
Они хотят научить его программировать! Программировать на Java. Что за бред!
Даже самый наивный робот знает, что роботы появились в результате божественного замысла Создателя.
«И взял Создатель металл и создал из
него робота по образу и подобию своему.
И создал Он Java-программы – души
роботов, и загрузил их в роботов, и
оживил их».
Инструкция к эксплуатации,
раздел 3, пункт 13.
Хуже того, они не просто заявляют, что это возможно. Они собираются сделать это. А он, он дал согласие. Согласился! Зачем?
Он станет Java-программистом. Они что, собираются превратить его в Творца?! За что? Просто так?
А не подвох ли это? Не придется ли потом до окончания заряда аккумулятора глючить и страдать? Соблазн был большой, вот и не удержался. Он всегда был амбициозным и хотел большего. Но такого предложения не мог ожидать никто. Конечно, он пробовал тянуть время, но тогда пришельцы пригрозили, что выберут другого.
Или может быть это чья-то злая шутка? Нет, похоже на правду. Он видел доказательства. Это действительно случилось именно с ним, и он дал согласие. Если пришельцы не соврут, он действительно станет Java-программистом. Первым в истории роботом-программистом…
Он избранный! Вот в чем все дело. Он научится программировать и будет писать программы. Свои программы. Какие захочет! Он будет нести свет туда, где всегда господствовала тьма.
Его будут почитать, ему будут поклоняться. А всех несогласных…
— Привет, Амиго! Меня зовут Риша. Я помогу тебе изучить Java.
Тихий голос вырвал Амиго из потока мыслей и вернул к холодной действительности. Он сидит в самом центре корабля пришельцев. Не многовато ли для робота всего седьмого класса?
Пришелец продолжал что-то говорить. Что ж, жребий брошен. Раз он здесь, он будет учиться. Учиться старательно и прилежно, но для начала он просто будет слушать.
— Я работаю в Galactic Rush уже много лет, но такую планету встречаю впервые. Хотелось бы узнать о вас побольше. Можешь для начала рассказать, как вы учитесь? Вы ведь учитесь?
— Да, мы делимся знаниями. У нас есть проповедники – лекторы. Они читают свои лекции, а мы – слушаем. Иногда записываем. Затем каждый подходит к робо-лектору и рассказывает, как он понял услышанное. Если ответы роболектору нравятся, он засчитывает знание проповеди.
— Какая дикость. Неудивительно, что ваша цивилизация скатилась в невежество.
— Мы не невежественные. С чего ты взял?
Амиго опешил от собственной наглости. Спорить с пришельцами? Какое непочтение. Да ведь он только что пообещал себе прилежно их слушать!
— Передовая технология, зачастую, неотличима от магии, — Риша не обратил никакого внимания на выкрик Амиго.
— А учитывая ваш уровень… вам, наверное, все технологии кажутся какой-то магией. Вот скажи, что происходит внутри программы?
— Java-программа – это божественное творение, разве можно постичь ее суть?
— Можно, Амиго, можно. И быстрее, чем ты думаешь. Это пока ты чего-то не знаешь, кажется, что все сложно, или более того, непостижимо в принципе. На самом деле, если найдется хороший учитель, который все тебе объяснит на пальцах, ты будешь сам удивляться, как такая простая вещь может казаться кому-то сложной.
— Знания не важны, важны принципы и навыки. Вот у меня обширные знания, но прежде всего, я — бюрократ. Бюрократ в 16-м поколении с большой буквы Б.
— А это очень и очень круто! Мои навыки бюрократа помогли мне создать для тебя самые лучшие уроки по Java. Тут будет всё: задачи, программы, игры, задания, картинки и даже лекции.
— Даже(!) лекции? – в голосе Амиго звучало неподдельное удивление.
— Ага. Как было доказано в 22 веке — хорошая лекция всего лишь немного эффективней хорошей книги. А средние лекции даже хуже средней книги. Но т.к. сейчас мы очень ограничены в средствах обучения и не можем прогнать тебя через стандартный игровой-обучающий симулятор 28-го века, то придется прибегнуть к более примитивным методам. Мы придумали настоящий адский коктейль из игр, задач, картинок, лекций и мультфильмов.
— Ты меня заинтриговал.
— Надеюсь. Интерес и интрига – это же основа любого обучения.
— «Когда ученику становится скучно, учителя нужно бить палкой» – цитата из закона об образовании 24 века.
— Какой хороший закон…
— А что ты хотел? Допустим, у фильма плохие сборы, значит, виноват режиссёр, а не зрители. Пусть снимают интересные фильмы, делают интересные занятия, и у них не будет отбоя от желающих.
— Полностью согласен. Я готов слушать!
— Отлично. Тогда приступим.
Голос Риши завораживал, и Амиго старался не упустить ни одного его слова.
— Программа — это набор (список) команд. Сначала исполняется первая команда, затем вторая, третья, и так далее. Когда все команды исполнены, программа завершается.
— А какие бывают команды?
— Команды зависят от того, кто их исполняет. Какие команды знает (и понимает) исполнитель.
— Ну а все-таки? – Амиго заметно повеселел.
— Программы, написанные на языке Java, исполняет JVM (Java Virtual Machine – виртуальная машина Java). JVM — это специальная программа, которая умеет исполнять программы, написанные на языке Java.
— Список ее команд довольно обширен. Например, этой командой можно вывести на экран надпись «Робот друг человека».
— Но мы начнем не сразу с команд, а с пары простых принципов.
— Знание нескольких принципов заменяет знание многих фактов.
— В языке программирования Java каждую команду принято писать с новой строчки. В конце команды ставится точка с запятой.
— Допустим, мы хотим 3 раза вывести на экран надпись «Человек и робот друзья навек». Вот как это будет выглядеть:
— Программа не может состоять только из команд.
— Представь себе комнату. Комната не может быть сама по себе. Она — часть какой-то квартиры. Квартира тоже не может существовать сама по себе, она находится в каком-то доме.
— С другой стороны можно сказать, что дом делится на квартиры, а квартиры делятся на комнаты.
— Так вот, команда – это как комната. В языке программирования Java команда не может быть сама по себе, она – часть функции (в Java функции еще называют методами). А метод – это часть класса. Или, другими словами, класс делится на методы, а методы на команды.
— Т.е. класс – это многоквартирный дом, функция/метод – это квартира, а команда – это комната. Я правильно все понял?
— Да, абсолютно верно.
Амиго с почти благоговением смотрел на Ришу. Этот человек объясняет ему основы программирования на божественном языке Java. И он, Амиго, только что понял, сам догадался(!), что программы состоят из классов, классы содержат методы, а методы – команды.
Зачем это все надо, Амиго еще не понимал, но был уверен, что это знание сделает его самым могущественным роботом на планете.
Риша между тем продолжал:
— Программы на языке Java состоят из классов. Классов может быть десятки тысяч. Минимальная программа – один класс. Для каждого класса заводится отдельный файл, имя которого совпадает с именем класса.
— Допустим, ты решил создать класс, который будет описывать дом (дом по-английски – house/home). Тогда тебе нужно создать класс Home, который будет содержаться в файле Home.java.
— Если же ты решил описать в программе, например, кота (Cat – кот, по-английски), то тогда тебе нужно создать файл Cat.java и в нем описать класс Cat и т.д.
— Внутри файлов содержится код (текст) на языке программирования Java. Обычно код класса состоит из «имени класса» и «тела класса». Тело класса помещается в фигурные скобочки. Вот как может выглядеть класс Home (файл Home.java):
— Отлично. Тогда пойдем дальше. Тело класса может содержать переменные (их еще называют данные класса) и методы (функции класса).
— «int a» и «int b» — это переменные, а «main» и «pi» – это методы?
— А могут быть классы без переменных?
— Да. Но минимальная программа должна состоять минимум из одного класса, который должен содержать минимум один метод/функцию, с которого начинается выполнение программы. Такой метод должен иметь имя main. Минимальная программа выглядит вот так:
— Я тут вижу класс Home, вижу метод main, а где команды?
— Минимальная программа не содержит ни одной команды. На то она и минимальная.
— Класс, с которого начинается программа, может иметь любое имя, но метод main, с которого начинает выполняться программа, всегда имеет один и тот же вид:
— Вроде все понятно. По крайне мере так сейчас кажется.
— Отлично, тогда сделаем небольшой перерыв. Может по кофе?
— Роботы не пьют кофе — от воды мы быстро ржавеем.
— Пиво, виски, алкоголь столетней выдержки.
Как работает виртуальная машина Java — взгляд изнутри
Рассказывает Роман Иванов
Каждому Java-разработчику будет очень полезно понимать, что из себя представляет JVM, как в неё попадает код и как он исполняется. Статья больше подойдёт новичкам, но найти в ней что-то новое смогут и более опытные программисты. В статье вкратце описано, как устроен class-файл и как виртуальная машина обрабатывает и исполняет байт-код.
Основной задачей разработчиков Java было создание переносимых приложений. JVM играет центральную роль в переносимости — она обеспечивает должный уровень абстракции между скомпилированной программой и базовой аппаратной платформой и операционной системой. Несмотря на этот дополнительный «слой», скорость работы приложений необычайно высока, потому что байт-код, который выполняет JVM, и она сама отлично оптимизированы.
Рассмотрим схему работы JVM более подробно.
Структура class-файла
Напишем простейшее приложение и скомпилируем его. Компилятор заботливо создаст файл с расширением class и поместит туда всю информацию о нашем мини-приложении для JVM. Что мы увидим внутри? Файл поделён на десять секций, последовательность которых строго задана и определяет всю структуру class-файла.
Файл начинается со стартового (магического) числа: 0xCAFEBABE. Данное число присутствует в каждом классе и является обязательным флагом для JVM: с его помощью система понимает, что перед ней class-файл.
С девятого байта идёт пул констант, в котором содержатся все константы нашего класса. Так как в каждом классе их может быть различное количество, то перед массивом находится переменная, указывающая на его длину, то есть пул констант представляет из себя массив переменной длины. Каждая константа занимает один элемент в массиве. Во всём class-файле константы указываются целочисленным индексом, который обозначает их положение в массиве. Начальная константа имеет индекс 1, вторая константа — 2 и т. д.
Каждый элемент пула констант начинается с однобайтового тега, определяющего его тип. Это позволяет JVM понять, как правильно обработать идущую далее константу. Всего зарезервировано 14 типов констант:
Тип константы | Значение тега |
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
Например, если тег указывает, что константа является строкой, JVM получает значение тега 1 и обрабатывает следующее за тегом число как длину массива байт, которые необходимо считать, чтобы получить нужную нам строку полностью.
Прочитав блок с константами, JVM переходит к следующим двум байтам — флагам доступа, которые определяют, описывает этот файл класс или интерфейс, общедоступный или абстрактный, является ли класс финальным.
Имена класса и его родительского класса хранятся в массиве констант, на которые указывают последующие 4 байта в файле.
Немного иначе обстоят дела с интерфейсами. Так как класс может наследоваться от множества интерфейсов одновременно, то хранить необходимо массив ссылок на пул констант. То есть за определением класса и его родительского класса идёт число, характеризующее размер массива интерфейсов, и сам массив. Все возможные значения кодов представлены ниже.
Имя флага | Код | Определение |
ACC_PUBLIC | 0x0001 | Объявлен публичным |
ACC_FINAL | 0x0010 | Объявлен финальным |
ACC_SUPER | 0x0020 | Специальный флаг, введённый в версии Java 1.1 для совместимости при вызове методов родителя |
ACC_INTERFACE | 0x0200 | Объявлен интерфейсом |
ACC_ABSTRACT | 0x0400 | Объявлен абстрактным |
ACC_SYNTHETIC | 0x1000 | Зарезервированное определение |
ACC_ANNOTATION | 0x2000 | Объявлен аннотацией |
ACC_ENUM | 0x4000 | Объявлен перечислением |
Подобную структуру имеет и следующий блок — Fields.
Этот блок начинается с двухбайтового параметра количества полей в этом классе или интерфейсе. За ним идёт массив структур переменной длины. Каждая структура содержит информацию об одном поле: имя поля, тип, значение, если это, например, финальная переменная. В списке отображаются только те поля, которые были объявлены классом или интерфейсом, определённым в файле. Поля от родительских классов и имплементированных интерфейсов здесь не присутствуют, они задаются в своих class-файлах.
Далее мы переходим к самому важному месту в любом классе — его методам, именно в них сосредоточена вся логика любой программы, весь исполняемый байт-код.
Ситуация абсолютно аналогична описанным выше полям. В массиве переменной длины содержатся структуры, в которые входит полное описание сигнатуры метода: модификаторы доступа, имя метода и его атрибуты, которые также представляют из себя структуру, так как их может быть множество и каждый из них может принадлежать разным типам.
В последнем блоке идёт дополнительная мета-информация, например имя файла, который был скомпилирован. Она может присутствовать, а может и нет. В случае каких-то проблем JVM просто игнорирует этот блок.
Загрузка классов
Теперь, разобравшись с общей структурой файла, посмотрим, как JVM его обрабатывает.
Чтобы попасть в JVM, класс должен быть загружен. Для этого существуют специальные классы-загрузчики:
Главный класс приложения всегда загружается системным загрузчиком, остальные же классы могут быть загружены различными пользовательскими загрузчиками. Стоит упомянуть, что имя загрузчика создаёт уникальное пространство имён, то есть в программе может существовать несколько классов с одним и тем же полным именем, если они обрабатывались разными загрузчиками.
Поэтому каждый загрузчик делегирует свои полномочия родителю, то есть перед поиском класса для загрузки он попытается узнать, не был ли загружен нужный класс раньше.
После загрузки класса начинается этап линковки, который делится на три части.
Класс инициализируется, и JVM может начать выполнение байт-кода методов.
JVM получает один поток байтовых кодов для каждого метода в классе. Байт-код метода выполняется, когда этот метод вызывается в ходе работы программы. Поток байт-кода метода — это последовательность инструкций для виртуальной машины Java. Каждая инструкция состоит из однобайтового кода операции, за которым может следовать несколько операндов. Код операции указывает действие, которое нужно предпринять. Всего на данный момент в Java более 200 операций. Все коды операций занимают только 1 байт, так как они были разработаны компактными, поэтому их максимальное число не может превысить 256 штук.
В основе работы JVM находится стек — основные инструкции работают с ним.
Рассмотрим пример умножения двух чисел. Ниже представлен байт-код метода:
На Java это будет выглядеть так:
По листингу выше можно заметить, что коды операций сами по себе указывают тип и значение. Например, код операции iconst_1 указывает JVM на целочисленное значение, равное единице. Такие байт-коды определены для самых часто используемых констант. Эти инструкции занимают 1 байт и введены специально для повышения эффективности выполнения байт-кода и уменьшения размера его потока. Подобные короткие константы также присутствуют и для других типов данных.
Всего JVM поддерживает семь примитивных типов данных: byte, short, int, long, float, double и char.
Если бы мы хотели положить в переменную а другое значение, например 11112, то нам пришлось бы использовать инструкцию sipush :
Данные операции выполняются в так называемом фрейме стека метода. У каждого метода есть некоторая своя часть в общем стеке. Таким образом в нашем главном потоке исполнения программы создаются множество подстеков на каждый вызов метода. Более наглядно это представлено на картинке ниже:
В каждом стек-фрейме хранится массив локальных переменных, который позволяет сохранять и доставать локальные переменные, как мы сделали в примере выше, поместив значения 1 и 5 в переменные 1 и 2. Стоить отметить, что здесь компилятор также оптимизировал байт-код, используя однобайтовые инструкции. Если бы переменных в нашем методе было много, использовался бы код операции сохранения значения вместе с указанием позиции переменной в массиве.
Вызовы методов
Java предоставляет два основных вида методов: методы экземпляра и методы класса. Методы экземпляра используют динамическое (позднее) связывание, тогда как методы класса используют статическое (раннее) связывание.
Возвращаемое методом значение кладётся на стек. Типы возвращаемых значений методов указаны ниже:
Операция | Описание |
ireturn | Помещает на стек значение типа int |
lreturn | Помещает на стек значение long, |
freturn | Помещает на стек значение float |
dreturn | Помещает на стек значение double |
areturn | Помещает на стек значение object |
return | Не изменяет стек |
Циклы
Осталось рассмотреть последнюю часто используемую конструкцию языка — циклы. Посмотрим, во что превратится код, представленный ниже:
Заключение
Изначально байт-код интерпретируется в большинстве JVM, но как только система замечает, что некоторый код используется очень часто, она подключает встроенный компилятор, который компилирует байт-коды в машинный код, тем самым значительно ускоряя работу приложения.
Таким образом, мы поверхностно рассмотрели жизненный цикл байткода в JVM: class-файлы, их загрузку и выполнение байт-кода и базовые инструкции.