задний план
Не так давно вышло третье издание "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 переменной, а не добавляет ее. , таким образом оптимизируя наш код и повышая эффективность выполнения.