Java №3. Что такое JVM
Мы привыкли к мы привыкли думать, что когда вы запускаете программу — компьютер исполняет машинный код (если упрощать уровень системы, гипервизора и т.д.). Но с Java работают немного другие правила, тут в игре байткод.
А кто этот ваш байткод вообще? Тут надо отступить и разъяснить, какие виды языков программирования и собранных приложений существуют. Если сильно упростить, языки часто делят на компилируемые и интерпретируемые. В реальности всё сложнее: один и тот же язык может иметь разные реализации, JIT-компиляцию, байткод и прочие радости жизни. Но для понимания JVM нам этого деления хватит.
Компилируемые
Основная идея компилируемых языков в том, что код программы переводится в понятный для компьютера машинный код. Исходники невозможно восстановить 1 в 1: имена локальных переменных, комментарии, форматирование и часть структуры уже потеряны. Декомпилятор может восстановить похожий код, но не оригинальный файл. При этом такой код заточен под определённую систему, а порой и архитектуру. Отсюда и знакомые вам windows_x64, linux_arm, macos_apple
Интерпретируемые
Идея интерпретируемых же языков в том, что код программы остаётся таким, каким он был написан. Для запуска используется программа-интерпретатор или runtime-среда (python, node). Из плюсов такого подхода — когда вы пишете код программы, вам зачастую не требуется задумываться о поддержке разных систем или копаться с компиляцией для них, вы отдаёте исходный код, который уже интерпретируется целевым компьютером.
А где тут Java?
А она встала ровно посередине. Сочетая плюсы и минусы обоих подходов.
Когда вы написали свою программу на Java, вам необходимо её скомпилировать, но тут и появляется важный нюанс, вместо компиляции в машинный код, Java компилируется в платформенно-независимый байткод. Но как его запустить? Тут и вступает в дело JVM (Java Virtual Machine), программа, которая читает и интерпретирует байт-код, но при этом, если какой-то кусочек используется очень активно, он может скомпилировать его в машинный код, используя JIT, что позволяет выигрывать и в скорости исполнения, и в удобстве разработки.
Но это теория, давайте посмотрим, как оно выглядит на самом деле! Давайте возьмём example программу от IDEA:
public static void main(String[] args) {
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
// to see how IntelliJ IDEA suggests fixing it.
System.out.printf("Hello and welcome!");
for (int i = 1; i <= 5; i++) {
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
System.out.println("i = " + i);
}
}
И теперь скомпилируем её в байткод:
| Адрес | Байт-код | Декомпилированное действие | Пояснение |
|---|---|---|---|
| 00000000 | B2 00 07 | getstatic java.io.PrintStream java.lang.System.out | Из класса System получаем статическое поле out, кладём в стек объект PrintStream |
| 00000003 | 12 0D | ldc “Hello and welcome!” | Загружаем строку “Hello and welcome!” из пула констант в стек. |
| 00000005 | 03 | iconst_0 | Кладём в стек число 0 |
| 00000006 | BD 00 02 | anewarray java.lang.Object | Создаём новый массив нулевого размера и кладём его в стек Так компилятор готовит пустой массив аргументов для varargs-метода printf. |
| 00000009 | B6 00 0F | invokevirtual java.io.PrintStream java.io.PrintStream.printf(java.lang.String, java.lang.Object[]) | Вызываем метод printf, он возвращает PrintStream |
| 0000000C | 57 | pop | Удаляем из стека полученный выше PrintStream |
| 0000000D | 04 | iconst_1 | Кладём в стек число 1 |
| 0000000E | 3C | istore_1 | Сохраняем число 1 в локальную переменную с индексом 1 (int i = 1) |
| 0000000F | 1B | iload_1 | Загружаем значение локальной переменной i |
| 00000010 | 08 | iconst_5 | Кладём в стек число 5 |
| 00000011 | A3 00 15 | if_icmpgt pos.00000026 | Забираем из стека i и 5. Если i > 5, то выполняем переход на адрес 00000026 |
| 00000014 | B2 00 07 | getstatic java.io.PrintStream java.lang.System.out | Из класса System получаем статическое поле out, кладём в стек объект PrintStream |
| 00000017 | 1B | iload_1 | Кладём в стек текущее значение i |
| 00000018 | BA 00 15 00 00 | invokedynamic java.lang.String BootstrapMethod[0]:makeConcatWithConstants(int) | Собираем строку "i = " + i. |
| 0000001D | B6 00 19 | invokevirtual void java.io.PrintStream.println(java.lang.String) | Вызываем println |
| 00000020 | 84 01 01 | iinc local.01, 1 | Увеличиваем локальную переменную с индексом 1 на единицу |
| 00000023 | A7 FF EC | goto pos.0000000F | Переходим на адрес 0000000F |
| 00000026 | B1 | return | Завершаем выполнение кода |
Давайте посмотрим как декомпилирует этот код IDEA
public static void main(String[] args) {
System.out.printf("Hello and welcome!");
for(int i = 1; i <= 5; ++i) {
System.out.println("i = " + i);
}
}
Довольно похоже, но это такая IDEA, вот вывод декомпилятора jar.tools:
public static void main(String[] args) {
System.out.printf("Hello and welcome!", new Object[0]);
for (int i = 1; i <= 5; i++) {
System.out.println("i = " + i);
}
}
И это маленький сниппет, на более крупных проектах разницы между разными декомпиляторами будут больше.
Но главная мысль вот в чём: Java-код не летит напрямую в процессор. Сначала он превращается в байткод, потом JVM берёт этот байткод и уже на месте решает, как его эффективнее выполнить. Отсюда и это требование наличие JVM.
Кстати, если вы хотите почитать статью про то, как я декомпилировал и патчил одно Java приложение, расширяя некоторые лимиты, вы можете почитать её тут - https://boosty.to/redguy/posts/51d4ba44-6938-40ae-8c2f-bff0436b5ac1?isFromShowcase=true
Продолжить чтение
Похожие материалы
Java №5. Банки Java
Время поговорить о том, как хранится ваше java приложение. Вы когда нибудь кидали мод в папку mods
СтатьяКатастрофа DHC-6-400 в Непале 2016 года
Авиационная катастрофа самолёта DHC 6 произошедшая 24 февраля 2016 года в Непале, около деревни Дан
СтатьяКатастрофа Ми-8 в Ямало-Ненецком автономном округе 2008 года
Авиационная катастрофа вертолёта Ми 8 произошедшая 2 июля 2008 года в 340 км от Нижневартовска. Из
СтатьяJava №6. А что дальше?
Ну и давайте вообще поговорим, чем вообще может стать Java для вас (в особенности для более молодых
СтатьяКатастрофа Boeing 747 под Токио 1985 года
Авиационная катастрофа самолёта Boeing 747 произошедшая 12 августа 1985 года под Токио. Из находивш
СтатьяКатастрофа Ил-18 под Казанью 1963 года
Авиационная катастрофа самолёта Boeing 747 произошедшая 12 августа 1985 года под Токио. Из находивш
СтатьяУгон Ан-24 в Турцию 1970 года
Авиационный угон самолёта Ан 24 произошедший 15 октября 1970 года на рейсе из Батуми в Сухуми. Из н
СтатьяПожар на лыжном спуске в Капруне 2000 года
Технологическая катастрофа на фуникулёре произошедшая 11 ноября 2000 года в Капруне. В результате т
СтатьяКатастрофа Ту-154 под Максатихой 1978 года
Авиационная катастрофа самолёта Ту 154 произошедшая 19 мая 1978 года в 5 км от Максатихи. Из находи