Внутренности JVM, Часть 2 — Структура class-файлов
Всем привет! Перевод статьи подготовлен специально для студентов курса «Разработчик Java».
Продолжаем разговор о том, как Java Virtual Machine работает внутри. В предыдущей статье (оригинал на анг.) мы рассмотрели подсистему загрузки классов. В этой статье мы поговорим о структуре class-файлов.

Рисунок — компиляция исходного кода Java
Давайте напишем простую программу.
Запуск javac для этого файла приведет к появлению следующих файлов.
Как видите, для каждого класса и интерфейса создается отдельный class-файл.
Что внутри class-файла?
Class-файл имеет бинарный формат. Информация в нем обычно записывается без отступов между последовательными частями информации, все выравнивается по границам байтов. Все 16-битные и 32-битные значения записываются с помощью двух или четырех последовательных 8-битных байтов.
Class-файл содержит следующую информацию.
Версия файла. Следующие четыре байта содержат мажорную и минорную версию файла. Вместе эти номера определяют версию формата class-файла. Если class-файл имеет основной мажорную версию M и минорную m, то мы обозначаем эту версию как M.m.
У каждой JVM есть ограничения по поддерживаемым версиям class-файлов. Например, Java 11 поддерживает major версию с 45 до 55, Java 12 — с 45 по 56.
Пул констант. Таблица структур, представляющих строковые константы, имена классов, интерфейсов, полей, методов и другие константы, которые есть в структуре ClassFile и ее подструктурах. Каждый элемент пула констант начинается с однобайтового тега, определяющего тип константы. В зависимости от типа константы следующие байты могут быть непосредственным значением константы или ссылкой на другой элемент в пуле.
This class. Ссылка на запись в пуле констант.
Super class. Ссылка на запись в пуле констант.
Интерфейсы. Количество интерфейсов, реализованных классом.
Количество полей. Количество полей в классе или интерфейсе.
Поля. После количества полей следует таблица структур переменной длины. По одной для каждого поля с описанием типа поля и названия (со ссылкой на пул констант).
Количество методов. Количество методов в классе или интерфейсе. Это число включает только методы, которые явно определены в классе, без методов, унаследованных от суперклассов.
Методы. Далее находятся сами методы. Для каждого метода содержится следующая информация: дескриптор метода (тип возвращаемого значения и список аргументов), количество слов, необходимых для локальных переменных метода, максимальное количество слов стека, необходимых для стека операндов метода, таблицу исключений, перехватываемых методом, байт-коды метода и таблица номеров строк.
Количество атрибутов. Количество атрибутов в этом классе, интерфейсе или модуле.
Атрибуты. После количества атрибутов следуют таблицы или структуры переменной длины, описывающие каждый атрибут. Например, всегда есть атрибут “SourceFile”. Он содержит имя исходного файла, из которого был скомпилирован class-файл.
Хотя class-файл напрямую не человекочитаемый, в JDK есть инструмент под названием javap, который выводит его содержимое в удобном формате.
Давайте напишем простую программу на Java, указанную ниже.
Здесь вы можете увидеть, что класс публичный ( public ) и у него в пуле констант 37 записей. Есть один атрибут (SourceFile внизу), класс реализует два интерфейса (Serializable, Cloneable), у него нет полей и есть два метода.
Больше почитать про javap вы можете здесь.
Совет: вы также можете использовать javap для того, чтобы увидеть, чем лямбды отличаются от анонимных внутренних классов.
Компиляция java кода из командной строки
Сегодня я расскажу как устроен базовый проект Java, как компилируется код и как выполнить готовую программу.
Java Source и каталоги классов
Простой Java-проект содержит один каталог, внутри которого хранятся все исходные файлы. Файлы обычно хранятся не внутри исходного каталога, а в подкаталогах, соответствующих их структуре пакета. Пакеты – это просто способ сгруппировать исходные файлы, которые принадлежат друг другу. Исходный каталог часто называют src, но это не является обязательным требованием.
Например, если вы используете инструмент сборки Maven, вы, как правило, будете использовать другую структуру каталогов, где исходный код Java хранится в каталоге src/main/java(в корневом каталоге вашего проекта).
Этот каталог часто называют классами, но, опять же, он не является обязательным, и он зависит от того, какой инструмент сборки используете, IDE и т. д.
Компиляция исходного кода Java
Вы можете скомпилировать исходный код Java непосредственно из вашей IDE(если вы используете IDE). Или вы можете использовать компилятор, который поставляется вместе с Java SDK. Чтобы выполнить компиляцию java кода из командной строки, сделайте следующее:
Каталог myfirstapp – это пакет в корневом каталоге исходного кода src. Если у вас есть несколько пакетов в корневом каталоге, вам придется запускать компилятор несколько раз. Java IDE обрабатывает это автоматически. Так же как и инструменты для сборки, такие как Ant, Maven или Gradle.
Выполнение скомпилированного кода
Когда вы запустите класс, ваша командная строка будет выглядеть примерно так(включая вывод из приложения):
Обратите внимание, что в первой команде не должно быть разрыва строки. Я добавил это только для того, чтобы было легче читать.
С каким расширением хранится исходный код классов а с каким скомпилированный java
В других языках, например, C++, этап компиляции в байт-код отсутствует: компилятор сразу преобразует текст программы (код) в машинный код и создает бинарный файл для исполнения (в Windows – это файл с расширением exe). Основным недостатком данного подхода является зависимость от операционной системы и процессора: для каждой операционной системы и процессора необходимо скомпилировать свою версию машинного кода.
Для того, чтобы скомпилировать и запустить программу на языке Java, необходимо выполнить следующий перечень действий:
1) создать файл с расширением java, содержащий текст программы;
2) скомпилировать его с помощью компилятора javac. Компилятор проверит код на ошибки и, если он корректен, выдаст файл с расширением class;
3) полученный файл, содержащий байт-код, запустить с помощью виртуальной машины Java.
Компилятор javac в Windows
Перейдем в командную строку Windows (cmd) и введем команду «javac».
В результате Вы можете наблюдать следующее сообщение: «»javac» не является внутренней или внешней командой, исполняемой программой или пакетным файлом». Также Вы можете ввести команду «java» и увидеть аналогичное сообщение: «»java» не является внутренней или внешней командой, исполняемой программой или пакетным файлом». Для исправления этой ситуации необходимо выполнить следующие действия:
1) Перейти по пути « C:\Program Files\Java\jdk-10.0.2\bin » (если Вы оставили путь для установки JDK по умолчанию, как это было в предыдущем уроке). В этой папке Вы можете наблюдать исполняемые файлы java.exe и javac.exe.
2) Переходим в окно Windows «Этот компьютер». На свободном пространстве нажимаем правой кнопкой мыши, должно появиться контекстное меню. В нём выбираем пункт «Свойства».
3) В появившемся окне «Система» нажимаем на «Изменить параметры».
4) В появившемся окне «Свойства системы» во вкладке «Дополнительно» нажимаем на кнопку «Переменные среды…».
5) В появившемся окне «Переменные среды» во вкладке «Системные переменные» находим переменную «Path» и нажимаем на кнопку «Изменить…».
6) В появившемся окне «Изменить переменную среды», нажимаем на кнопку «Создать» и добавляем новое значение: « C:\Program Files\Java\jdk-10.0.2\bin\ ». Принимаем изменения и нажимаем кнопку «ОК».
7) Принимаем изменения во всех остальных открытых нами окнах, нажимая кнопку «ОК».
8) Переходим в командную строку и вводим «javac» или «java».
Система выдаст нам справку, в которой показано как использовать (usage) команду и перечислены её возможные опции (options). Source files (дословно «источники файлов») – это пути к компилируемым нами файлам.
Компиляция программы и её запуск на исполнение
Скомпилируем нашу первую программу и запустим её.
Создадим файл Cat.java (Помните же, что имя класса должно соответствовать имени файла?) и поместим в этот файл следующий код:
public class Cat <
public static void main(String[] args) <
System.out.println(«Привет, я кот! Твой друг ;)»);
>
>
В командной строке введём команду «javac» и через пробел укажем путь к созданному файлу. Так, если Вы создали файл Cat.java на рабочем столе Windows, то путь к файлу будет следующим: « C:\Users\ USERNAME \Desktop\Cat.java », где USERNAME – имя Вашего пользователя.
Иногда могут возникать сложности при создании файла с расширением java: используя стандартный текстовый блокнот в Windows, новички просто изменяют имя файла, и в итоге вместо файла Cat.java создается файл Cat.java.txt. Для создания файла именно с расширением java, можно воспользоваться каким-нибудь текстовым редактором, например, Notepad++. Будьте внимательны!
В процессе компиляции исходного кода каждый отдельный класс помещается в собственный выходной файл, называемый по имени класса и получающий расширение class. Его содержимое можно открыть также текстовым редактором, например, Notepad++.
Чтобы выполнить программу, следует воспользоваться загрузчиком приложений java. При запуске необходимо указать имя класса, который нужно выполнить. Загрузчик приложений java автоматически будет искать файл с указанным именем и расширением class. И если он найдет такой файл, то выполнит код, содержащийся в указанном классе.
Введём в командной строке «java Cat» и получим ошибку «Не могу найти или загрузить основной класс Cat»:
Error: Could not find or load main class Cat
Caused by: java.lang.ClassNotFoundException: Cat
Введём в командной строке теперь верную команду: « java –classpath C:\Users\ USERNAME \Desktop Cat ». Здесь мы использовали опцию classpath («путь к файлу с расширением class»). Для изучения схемы использования команды и возможных опций, как Вы уже догадались, необходимо ввести команду «java».
В командной строке должно появиться сообщение: «Ваша первая программа на Java». Если Вы видите кракозябры, Вам необходимо изменить кодировку командной строки. Это делается командой chcp в формате: « chcp ». Чтобы изменить кодировку в командной строке на UTF-8, необходимо выполнить команду: « chcp 65001 ».
Скомпилируйте и запустите программу для класса Dog. В выводимое сообщение на экран внесите соответствующие изменения.
В следующем уроке мы установим интегрированную среду разработки, создадим первый проект и запустим нашу программу.
Как именно происходит компиляция java?
запутался в процессе компиляции java
хорошо, я знаю это: мы пишем исходный код java, компилятор, который не зависит от платформы, переводит его в байт-код, а затем jvm, который зависит от платформы, переводит его в машинный код.
может кто-нибудь сказать мне, ясно и подробно о том, как мой исходный код Java преобразуется в машинный код.
9 ответов:
хорошо, я знаю это: мы пишем исходный код java, компилятор, который не зависит от платформы, переводит его в байт-код,
собственно сам компилятор работает как собственный исполняемый файл (отсюда javac.исполняемый.) И правда, он преобразует исходный файл в байт-код. Байт-код не зависит от платформы, поскольку он предназначен для виртуальной машины Java.
затем jvm, который зависит от платформы, переводит его в машину код.
не всегда. Как для JVM от Sun существует две виртуальные машины: клиентом и сервером. Они оба могут, но не обязательно должны компилироваться в машинный код.
этой exe файл представляет собой обернутый байт-код java. Это удобство-избежать сложных пакетных сценариев. Он запускает JVM и выполняет компилятор.
если код компилятора написан на java, то как код компилятора выполняется на этапе компиляции, так как его работа jvm для выполнения кода java.
это именно то, что делает код упаковки.
как может сам язык компилировать свой собственный код языка? Все это похоже на проблему курицы и яйца мне.
правда, сбивает с толку на первый взгляд. Хотя, это не только идиома Java. Компилятор Ada также написан в самой Ada. Это может выглядеть как «проблема курицы и яйца», но на самом деле это проблема только загрузчик.
так что вы уже должны иметь представление, что это такое на самом деле.
может кто-нибудь сказать мне, ясно и подробно о том, как мой исходный код Java преобразуется в машинный код.
Я думаю, что это должно быть более ясно прямо сейчас, но вот краткое резюме:
вызов javac указывая на исходный код файл. Внутренний читатель (или токенизатор) javac считывает ваш файл и создает из него фактический AST. Все синтаксические ошибки происходят с этого этапа.
The javac еще не закончил работу. Когда у него есть AST, может начаться истинная компиляция. Он использует шаблон посетителя для обхода AST и разрешает внешние зависимости, чтобы добавить значение (семантику) в код. Готовый продукт сохраняется как содержащий байткод.
правка: без javac можно было бы вызвать компилятор, используя что-то вроде этого:
таким образом, стандартный способ-отправить javac обертка с JDK.
вы также можете взглянуть на Спецификация Виртуальной Машины, которая включает:
многие (если не большинство) компиляторы написаны на языке, который они компилируют. Очевидно, что на каком-то раннем этапе компилятор сам должен был быть скомпилирован чем-то иначе, но после этой «начальной загрузки», любая новая версия компилятора может быть скомпилирована более старой версией.
ну, javac и jvm обычно являются собственными двоичными файлами. Они написаны на Си или что-то еще. Конечно, можно написать их на Java, просто вам сначала нужна родная версия. Это называется «привязка ботинка».
забавный факт: большинство компиляторов, которые компилируются в машинный код, написанный на их родном языке. Однако все они должны были иметь родную версию, написанную на другом языке (обычно C). Первый компилятор C, для сравнения, был написан на ассемблере. Я предполагаю, что первый ассемблер был написан в машинном коде. (Или,С помощью бабочек;)
.файлы классов-это байт-код, генерируемый javac. Они не текстовые, это двоичный код, похожий на машинный код (но с другим набором команд и архитектурой).
jvm во время выполнения имеет два варианта: он может либо интепретировать байтовый код (притворяясь самим процессором), либо он может JIT (just-in-time) скомпилировать его в собственный машинный код. Последнее, конечно, быстрее, но все гораздо сложнее.
Длинное Объяснение
всегда помните, что java не является базовый язык, который распознает операционная система. Исходный код Java интерпретируется операционной системой с помощью переводчика под названием виртуальная машина Java (JVM). JVM не может понять код, который вы пишете в Редакторе, ему нужен скомпилированный код. Вот где компилятор входит в картину.
каждый компьютерный процесс позволяет себе манипуляции с памятью. Мы не можем просто написать код в текстовом редакторе и скомпилировать его. Нам нужно поместить его в память компьютера, то есть сохранить его до того, как скомпилировать.
факты, которые вы должны знать
1) Java не Multi-платформы это независимая платформа.
2) JVM является разработано с использованием C / C++. Одна из причин, почему люди называют Java более медленным языком, чем C/C++
Windows не знает, как вызывать программы Java перед установкой среды выполнения Java, и Sun выбрала собственные команды, которые собирают аргументы, а затем вызывают JVM вместо привязки jar-суффикса к движку Java.
компилятор изначально был написан на C с битами C++, и я предполагаю, что он все еще есть (почему вы думаете, что компилятор также написан на Java?). и javac.exe-это просто код C / C++, который является компилятором.
в качестве побочной точки вы можете написать компилятор на java, но вы правы, вы должны избегать проблемы курицы и яйца. Для этого вы обычно пишете один или несколько инструментов начальной загрузки в чем-то вроде C, чтобы иметь возможность компилировать компилятор.
чтобы немного усложнить это, JVM также оптимизирует и кэширует машинный код, полученный из байт-кодов, чтобы избежать их повторного перевода. Это известно как компиляция JIT и происходит как программа работает и байт-коды интерпретируются.
Pro Java
Страницы
3 апр. 2015 г.
Разбираемся с classpath в Java. Часть 1.
Теперь быстренько разберемся classpath, так как это достаточно важная тема для разработки на Java. Естественно я тут не смогу разобрать все тонкости и хитрости, но постараюсь представить самое необходимое чтобы было понимание этой темы. Для желающих разобраться глубже приведу линки для самостоятельного изучения. Ну и как всегда гугль в помощь.
Чтобы понимать что происходит под капотом у любой Java IDE когда она собирает проект и запускает его надо хоть немного попрактиковаться в использовании компилятора javac, среды исполнения java и понять classpath.
По существу classpath указывает компилятору или виртуальной машине где искать классы необходимые для сборки проекта или же его запуска. Немного об этом мы уже узнали тут. Теперь разберемся с этим получше.
Указать где компилятору или виртуальной машине искать классы можно через ключ –classpath или же системную переменную окружения CLASSPATH. Мы рассмотрим оба этих варианта.
Начнем с простого. Вернемся к нашему проекту Hello World (00004E_HelloWorld), там где мы разделили его на два файла Hello.java и Word.java.
Теперь попробуем создать исполняемый (jar) файл этого проекта из среды Eclipse. Так как скомпилированные, читай готовые к исполнению, файлы в Java имеют расширение class, а классов в реальных программах, могут быть сотни и тысячи, то их собирают в один или несколько jar архивов и таким образом запускают. То есть уже существует не россыпь файлов с расширением class, а один или несколько jar файлов.
И так! Понеслась! Воспользуемся Export для создания jar
После этого мы получим файл HelloWorld.jar готовый к исполнению на виртуальной машине java. Запустим его из командной строки:
Запускать jar файлы надо с ключом –jar как показано на скрине выше. Если этот ключ не использовать то вылетит ошибка:
Почему строчка запуска выглядит именно так? Вспоминаем что именно класс Hello.java содержит у нас метод main.
Класс Word.java такого метода не имеет.
Как я уже говорил метод main – это точка входа в программу, то есть место от куда начинается ее выполнение и поэтому виртуальной машине java надо знать, от куда надо начинать выполнять программу. Если она не может найти метод main, то она начинает ругаться, как это было показано выше.
И так в нашей строчке
Для начал просто скомпилируем исходники в class файлы без упаковки их в jar, чтобы было понятнее.
Переходим в коневой каталог 00004E_HelloWorld и от туда даем команду компиляции
Поскольку у нас программа состоит из двух классов Hello и Word, то их обоих сразу надо указать компилятору. Кроме того так же надо указать и кодировочку исходников. Так же мы указали папку bin – это то куда будут складываться откомпилированные файлы.
Теперь у нас в каталоге bin два файла Hello.class и Word.class. Перейдем в него чтобы запустить программу.
jar cf HelloWorld.jar Hello.class Word.class
и попробуем запустить HelloWorld.jar
И вылетела ошибочка. Почему так? Ведь у нас уже есть jar файл в который упакованы оба класса.
Но все равно не работает. Это происходит потому, что внутри jar файла мы не указали какой файл у нас имеет метод main.
Запустить наш jar файл все таки можно указав дополнительно, какой класс содержит метод main.
Теперь все работает. Но согласитесь так запускать jar файл не удобно, так как всегда надо знать какой класс содержит метод main. Если вы смотрели внимательно, то видимо заметили внутри архива HelloWorld.jar папку META-INF. В ней содержится файл MANIFEST.MF
Вот в нем и должна содержаться информация о классе содержащем метод main, но пока в нем ее нет.
Исправим эту ошибочку. Удалим файлик HelloWorld.jar и создадим его заново, но уже с добавлением информации о классе содержащим метод main. Сделаем это следующей командой
jar cfe HelloWorld.jar Hello Hello.class Word.class
И запустим файл HelloWorld.jar уже как полагается без танцев с бубном.
Как видим все работает нормально. Это произошло потому, что файл MANIFEST.MF уже содержит информацию о классе содержащем метод main.
Ну вот теперь мы имеем хоть какое-то представление о том что происходит когда какая-либо IDE создает исполняемый jar файл, а так же получили представление о classpath. В следующей статье мы немного углубим его.
P.S. Так же стоит знать что по умолчанию для виртуальной машины java доступны все классы стандартной библиотеки java, а так же все классы в текущем каталоге от куда запускается главный класс содержащий метод main.
Ну и на последок ссылка где про classpath рассказано достаточно подробно. Правда я не знаю как долго она проживет.



