Блог · Бессонный Кодер
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
Продолжить чтение
Похожие материалы
Катастрофа DHC-6-400 в Непале 2016 года
Авиационная катастрофа самолёта DHC 6 произошедшая 24 февраля 2016 года в Непале, около деревни Дан
СтатьяКатастрофа Ми-8 в Ямало-Ненецком автономном округе 2008 года
Авиационная катастрофа вертолёта Ми 8 произошедшая 2 июля 2008 года в 340 км от Нижневартовска. Из
СтатьяКатастрофа 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 км от Максатихи. Из находи
СтатьяРазмышляем о Головоломке: важность печали и опасность экстремального оптимизма
Совсем недавно (как минимум по меркам даты написания этого сценария), на большие экраны вышла Голов
СтатьяЭтика игр №6. Зависимость от игр
А вы задумывались, откуда и почему вообще появляется эта страшная и непонятная — игровая зави