Это программа, которую знает каждый программист на Java. Я хочу посмотреть, что можно узнать из этой простой программы. Простое начало может облегчить изучение более сложных вещей. Было бы здорово, если бы этот пост было интересно читать, не только для программистов начального уровня. Пожалуйста, оставьте свои комментарии, если привет мир значит больше для вас.
HelloWorld.java
public class HelloWorld { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("Hello World"); } }
1. Почему все начинается с класса?
Java-программы построены из классов, каждый метод и поле должны быть в классе. Это связано с его объектно-ориентированной особенностью. Объектно-ориентированные языки программирования имеют много преимуществ, таких как модульность, расширяемость и т. Д.
2. Затем «основной» метод — вход в программу
«Основной» метод — это вход в программу, и он статический. «Статический» означает, что метод является частью своего класса, а не частью объектов.
Почему это? Почему мы не помещаем нестатический метод в качестве входа в программу?
Если метод не является статическим, то для его использования необходимо сначала создать объект. Потому что метод должен быть вызван на объекте. Для входа это не реально. Таким образом, метод входа в программу является статическим.
Параметр «String [] args» указывает, что в программу можно отправить массив строк, чтобы помочь с инициализацией программы.
3. Байт-код HelloWorld
Для выполнения программы файл Java сначала компилируется в байтовый код Java, хранящийся в файле .class.
Как выглядит байт-код?
Сам байт-код не читается. Если мы используем шестнадцатеричный редактор, это выглядит следующим образом:
В приведенном выше байт-коде мы видим много кода операции (например, CA, 4C и т. Д.), Каждый из которых имеет соответствующий мнемонический код (например, aload_0 в приведенном ниже примере). Код операции не читается, но мы можем использовать javap, чтобы увидеть мнемоническую форму файла .class.
«Javap -c» печатает дизассемблированный код для каждого метода в классе. Разобранный код означает инструкции, которые содержат байт-коды Java.
javap -classpath. -c HelloWorld
Compiled from "HelloWorld.java" public class HelloWorld extends java.lang.Object{ public HelloWorld(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String Hello World 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
Приведенный выше код содержит два метода: один является конструктором по умолчанию, который выводится компилятором; другой основной метод.
Ниже каждого метода есть последовательность инструкций, таких как aload_0, invokespecial # 1 и т. Д. То, что делает каждая инструкция, можно посмотреть в листингах инструкций байт-кода Java . Например, aload_0 загружает ссылку в стек из локальной переменной 0, gettatic извлекает значение статического поля класса. Обратите внимание, что «# 2» после gettatic указывает на пул констант во время выполнения. Пул констант является одной из областей данных времени выполнения JVM . Это заставляет нас взглянуть на пул констант, что можно сделать с помощью команды «javap -verbose».
Кроме того, каждая инструкция начинается с числа, такого как 0, 1, 4 и т. Д. В файле .class каждый метод имеет соответствующий массив байт-кода. Эти числа соответствуют индексу массива, в котором хранятся каждый код операции и его параметры. Каждый код операции имеет длину 1 байт, а инструкции могут иметь 0 или несколько параметров. Вот почему эти цифры не являются последовательными.
Теперь мы можем использовать «javap -verbose» для дальнейшего изучения класса.
javap -classpath. -вербоз HelloWorld
Compiled from "HelloWorld.java" public class HelloWorld extends java.lang.Object SourceFile: "HelloWorld.java" minor version: 0 major version: 50 Constant pool: const #1 = Method #6.#15; // java/lang/Object."<init>":()V const #2 = Field #16.#17; // java/lang/System.out:Ljava/io/PrintStream; const #3 = String #18; // Hello World const #4 = Method #19.#20; // java/io/PrintStream.println:(Ljava/lang/String;)V const #5 = class #21; // HelloWorld const #6 = class #22; // java/lang/Object const #7 = Asciz <init>; const #8 = Asciz ()V; const #9 = Asciz Code; const #10 = Asciz LineNumberTable; const #11 = Asciz main; const #12 = Asciz ([Ljava/lang/String;)V; const #13 = Asciz SourceFile; const #14 = Asciz HelloWorld.java; const #15 = NameAndType #7:#8;// "<init>":()V const #16 = class #23; // java/lang/System const #17 = NameAndType #24:#25;// out:Ljava/io/PrintStream; const #18 = Asciz Hello World; const #19 = class #26; // java/io/PrintStream const #20 = NameAndType #27:#28;// println:(Ljava/lang/String;)V const #21 = Asciz HelloWorld; const #22 = Asciz java/lang/Object; const #23 = Asciz java/lang/System; const #24 = Asciz out; const #25 = Asciz Ljava/io/PrintStream;; const #26 = Asciz java/io/PrintStream; const #27 = Asciz println; const #28 = Asciz (Ljava/lang/String;)V; { public HelloWorld(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 2: 0 public static void main(java.lang.String[]); Code: Stack=2, Locals=1, Args_size=1 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String Hello World 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 9: 0 line 10: 8 }
Из спецификации JVM : пул констант во время выполнения выполняет функцию, аналогичную функции таблицы символов для обычного языка программирования, хотя он содержит более широкий диапазон данных, чем типичная таблица символов.
«# 1» в инструкции «invokespecial # 1» указывает на константу # 1 в пуле констант. Константа «Метод № 6. № 15;». Из числа мы можем получить конечную константу рекурсивно.
LineNumberTable предоставляет отладчику информацию для указания, какая строка исходного кода Java соответствует какой инструкции байт-кода. Например, строка 9 в исходном коде Java соответствует байтовому коду 0 в основном методе, а строка 10 соответствует байтовому коду 8.
Если вы хотите узнать больше о байт-коде, вы можете создать и скомпилировать более сложный класс для просмотра. HelloWorld действительно является отправной точкой для этого.
4. Как это выполняется в JVM?
Теперь вопрос в том, как JVM загружает класс и вызывает метод main?
Перед выполнением основного метода JVM должна загрузить, связать и инициализировать класс. Загрузка приносит двоичную форму для класса / интерфейса в JVM. Связывание включает данные бинарного типа в состояние выполнения JVM. Связывание делится на 3 этапа: проверка, подготовка и дополнительное разрешение. Проверка гарантирует, что класс / интерфейс является структурно правильным. Подготовка включает выделение памяти, необходимой классу / интерфейсу. Разрешение разрешает символические ссылки. И, наконец, на этапе инициализации переменные класса получают правильные начальные значения.
Эта работа по загрузке выполняется Java Classloader. Когда JVM запускается, используются три загрузчика классов:
- Загрузчик класса Bootstrap: загружает основные библиотеки Java, расположенные в
Каталог / jre / lib. Он является частью базовой JVM и написан на нативном коде.
- Загрузчик класса расширений: загружает код в каталоги расширений (например,
/ Банка / Библиотека / вн).
- Загрузчик системного класса: загружает код, найденный на CLASSPATH.
Так класс HelloWorld загружается классом системы. Когда основной метод выполняется, он запускает загрузку, связывание и инициализацию других классов, если они существуют.
Наконец, кадр main () помещается в стек JVM, и программный счетчик (ПК) устанавливается соответствующим образом. Затем ПК указывает, что нужно отправить кадр println () в стек JVM. Когда метод main () завершится, он выскочит из стека и выполнение будет завершено. ( источник )
Ссылки:
1. Java Bytecode
2. Загрузка
3. Механизм загрузки классов
4. Загрузчик классов