← На главнуюСерия статейJava: ВступлениеВступительный цикл постов про устройство Java, JVM и разработке на них.

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

R
АвторБессонный Кодер

Пишу о разработке, системах и играх.

Telegram-канал

Продолжить чтение

Похожие материалы

Статья

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 км от Максатихи. Из находи