задний план
Не так давно вышло третье издание "Understanding Java Virtual Machine". Я быстро купил его и ознакомился с содержанием новой версии. Эта книга обновила многое из содержания новой версии виртуальной машины. , а также подвергли рефакторингу часть предыдущего контента. Стоит посмотреть. С целью обзора и консолидации я решил скомпилировать простой файл класса для анализа содержимого байт-кода Java, чтобы помочь понять и закрепить знания о байт-коде Java, и я надеюсь, что это будет полезно для вас, кто читает эту статью.
Описание: На этот раз используется среда OpenJdk12.
Скомпилировать код "1+1"
В первую очередь нам нужно написать простенькую небольшую программу, программу 1+1, и начинать обучение следует с самой простой 1+1, код такой:
package top.luozhou.test;
/**
* @description:
* @author: luozhou
* @create: 2019-12-25 21:28
**/
public class TestJava {
public static void main(String[] args) {
int a=1+1;
System.out.println(a);
}
}
После записи файла класса java сначала выполните командуjavac TestJava.javaСкомпилируйте файл класса, сгенерируйтеTestJava.class.
Затем выполните команду декомпиляцииjavap -verbose TestJava, результат байт-кода отображается следующим образом:
Compiled from "TestJava.java"
public class top.luozhou.test.TestJava
minor version: 0
major version: 56
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V
#4 = Class #19 // top/luozhou/test/TestJava
#5 = Class #20 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 main
#11 = Utf8 ([Ljava/lang/String;)V
#12 = Utf8 SourceFile
#13 = Utf8 TestJava.java
#14 = NameAndType #6:#7 // "<init>":()V
#15 = Class #21 // java/lang/System
#16 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
#17 = Class #24 // java/io/PrintStream
#18 = NameAndType #25:#26 // println:(I)V
#19 = Utf8 top/luozhou/test/TestJava
#20 = Utf8 java/lang/Object
#21 = Utf8 java/lang/System
#22 = Utf8 out
#23 = Utf8 Ljava/io/PrintStream;
#24 = Utf8 java/io/PrintStream
#25 = Utf8 println
#26 = Utf8 (I)V
{
public top.luozhou.test.TestJava();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_2
1: istore_1
2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
5: iload_1
6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
9: return
LineNumberTable:
line 10: 0
line 11: 2
line 12: 9
}
Разобрать байт-код
1. Основная информация
Приведенные выше результаты удаляют некоторую избыточную информацию, не влияющую на синтаксический анализ, а затем мы проанализируем результат байт-кода.
minor version: 0 次版本号,为0表示未使用
major version: 56 主版本号,56表示jdk12,表示只能运行在jdk12版本以及之后的虚拟机中
flags: ACC_PUBLIC, ACC_SUPER
ACC_PUBLIC: это флаг доступа, указывающий, является ли это общедоступным типом.
ACC_SUPER: Этот falg должен решитьinvokespecialПроблема с директивой, вызывающей суперметод. Думайте об этом как об исправлении ошибки для Java 1.0.2, только благодаря этому он может правильно находить методы суперкласса. Начиная с Java 1.0.2 компилятор всегда генерирует флаг доступа ACC_SUPER в байт-коде. Заинтересованные студенты могут нажать здесьздесьУзнать больше.
2. Постоянный пул
Далее мы проанализируем пул констант, вы также можете понять общий байт-код выше.
#1 = Methodref #5.#14 // java/lang/Object."<init>":()V
Это ссылка на метод, здесь#5Представляет значение индекса, тогда мы можем обнаружить, что байт-код со значением индекса 5 выглядит следующим образом
#5 = Class #20 // java/lang/Object
это говорит, что этоObjectкласс, то самое#14указывает на"<init>":()VУказывает, что это ссылка на метод инициализации.
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
Вышеприведенный абзац указывает, что это ссылка на поле, которая также относится к#15а также#16, что на самом деле относится кjava/lang/Systemв классеPrintStreamобъект. Другие идеи анализа постоянного пула такие же. Из-за недостатка места я не буду объяснять их по одному. Ниже перечислены только несколько ключевых типов и информация.
NameAndType: это представление представляет собой постоянную таблицу имен и типов, которая может указывать на имя метода или индекс поля, что является фактическим методом, представленным в приведенном выше байт-коде.
Utf8:Мы часто используем кодировку символов, но это не означает только кодировку символов, что указывает на то, что кодировка символовUtf8Нить. Это наиболее часто используемая структура таблиц в виртуальных машинах.Вы понимаете, что она может описывать такую информацию, как методы, поля и классы.
Например:
#4 = Class #19
#19 = Utf8 top/luozhou/test/TestJava
это значит здесь#4Под этим индексом находится класс, а затем класс, на который указывает#19,#19ЯвляетсяUtf8таблица, конечное хранилищеtop/luozhou/test/TestJava, то после подключения мы можем знать, что#4Класс, на который ссылается местоположение,top/luozhou/test/TestJava.
3. Информация о методе строительства
Далее разберем байткод конструктора.Мы знаем, что при инициализации класса сначала выполняется его конструктор.Если вы не напишете конструктор, то система по умолчанию добавит вам конструктор без параметров.
public top.luozhou.test.TestJava();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
descriptor: ()V: указывает, что это метод без возвращаемого значения.
flags: ACC_PUBLIC: это общедоступный метод.
stack=1, locals=1, args_size=1: указывает, что число в стеке равно 1, переменная в таблице локальных переменных равна 1 и параметр вызова также равен 1.
Почему здесь все 1? Разве это не конструктор по умолчанию? Откуда взялись параметры? На самом деле в языке Java есть негласное правило:В любом методе экземпляра вы можете передатьthisдля доступа к объекту, которому принадлежит этот метод. Реализация этого механизма передается в метод в качестве входного параметра через компилятор Java при компиляции, знакомый сpythonИзучающие языки обязательно узнают, что вpythonМетод, определенный в, всегда будет передаваться вselfПараметр, который также является ссылкой на этот экземпляр, переданный в метод, Java просто подталкивает этот механизм к этапу компиляции для завершения. Таким образом, 1 здесь относится кthisТолько этот параметр.
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
После вышеприведенного анализа значение этого метода построения становится очень ясным.
aload_0: указывает, что первая переменная в таблице локальных переменных загружается в стек, т. е.this.
invokespecial: Непосредственный вызов метода инициализации.
return: вызов завершен, и метод завершается.
LineNumberTable:Это таблица количества строк, в которой записано соответствие между смещениями байт-кода и строками кода.line 8: 0Указывает, что 8-я строка в исходном коде соответствует смещению0Байт-код, так как это метод построения по умолчанию, не может быть здесь отражен напрямую.
Кроме того, здесь будет выполнятьсяObjectпостроен потому, чтоObjectЭто родительский класс для всех классов, и конструктор подкласса должен сначала создать конструктор родительского класса.
4. Информация об основном методе
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: iconst_2
1: istore_1
2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
5: iload_1
6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
9: return
LineNumberTable:
line 10: 0
line 11: 2
line 12: 9
После анализа предыдущего метода построения мы затем анализируемmainМетод также будет многим знаком, и я пропущу повторение.Вот ключевой анализ.codeчасть.
stack=2, locals=2, args_size=1: Стек и таблица локальных переменных здесь равны 2, а параметр по-прежнему равен 1. Почему это? потому чтоmainВ методе объявлена переменнаяa, поэтому таблицу локальных переменных нужно добавить на единицу, и стек тоже, поэтому их 2. так почемуargs_sizeИли 1? Вы не говорите, что по умолчаниюthisВходящий? Должно быть 2.Примечание. То, что я сказал ранее, относится к любому методу экземпляра, и этот основной метод является статическим методом. Доступ к статическому методу можно получить непосредственно через имя класса + метода, и ему не нужен объект экземпляра, поэтому нет необходимости передавать это здесь..
0: iconst_2:БудуintТип 2 помещается на вершину стека.
1: istore_1: положить вершину стекаintЗначение типа хранится во второй локальной переменной.
2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;:ПолучатьPrintStreamДобрый.
5: iload_1: поставь второйintЛокальная переменная типа помещается на вершину стека.
6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V:передачаprintlnметод. здесьprintlnМетод выполнит элемент наверху стека как свой собственный входной параметр и в конечном итоге выведет 2.
9: return: Вызвать метод конца, когда закончите.
здесьLineNumberTableИсходный код есть, можем проверить правильность моего предыдущего описания:
line 10: 0: Строка 10 означает0: iconst_2Байт-код, здесь мы обнаруживаем, что компилятор напрямую вычисляет за нас и заталкивает 2 на вершину стека.
line 11: 2: Строка 11 исходного кода соответствует2: getstaticСтатический класс для получения выводаPrintStream.
line 12: 9: 12 строк исходного кода соответствуютreturn, указывающий на конец метода.
Здесь я также рисую динамическую картинку, чтобы продемонстрироватьmainПроцесс выполнения метода, надеюсь, помог вам понять:
Суммировать
В этой статье я начал с компиляции исходного кода 1+1 и проанализировал сгенерированный байт-код Java, включая основную информацию о классе, пуле констант, процессе вызова метода и т. д. Благодаря этому анализу у нас есть хорошее понимание Java. У меня есть базовое понимание, и я также знаю, что компилятор Java отразит метод оптимизации через скомпилированный байт-код, такой как наш 1 + 1 = 2, байт байт-кода присваивает 2 переменной, а не добавляет ее. , таким образом оптимизируя наш код и повышая эффективность выполнения.