предисловие
Для понимания основных принципов лучше всего использовать практический опыт;
когда начать
пример
1package com.java.study.jvm;
2
3/**
4 * @author zhangpeng
5 * @since 2020/1/15 3:33 下午
6 */
7public class JvmHello {
8 public static final int i = 2020;
9
10 public static void main(String[] args) {
11 JvmHello jvmHello = new JvmHello();
12 int a = 1;
13 int b = 2;
14 int c = jvmHello.calculate1(a, b);
15 int d = jvmHello.calculate2(a, b);
16 }
17
18 private int calculate2(int a, int b) {
19 int x = 666;
20 return x / (a + b);
21 }
22
23 private int calculate1(int a, int b) {
24 return (a + b) * 2333;
25 }
26}
Я не буду объяснять этот код, просто скомпилируйте байт-код и запустите его.
1# 编译生成 JvmHello.class文件
2javac JvmHello.java
3# 反编译字节码内容
4javap -verbose -p JvmHello.class
Помните, что я упоминал в предыдущей книге, скомпилируйте один раз и выполните везде, затем сначала файл должен быть загружен и запущен в среде; таким образом, у нас есть предварительная диаграмма
JvmHello.java -> JvmHello.class -> загружается система загрузки классов -> выполняется в среде виртуальной машины
Затем мы смотрим на содержимое JVMHello.class.
1Classfile /Users/zhangpeng/workspacke/mytest/study/src/main/java/com/java/study/jvm/JvmHello.class
2 Last modified 2020-1-15; size 530 bytes
3 MD5 checksum d1725552383bf6c86a00f1517d2b4c51
4 Compiled from "JvmHello.java"
5public class com.java.study.jvm.JvmHello
6 minor version: 0
7 major version: 52
8 flags: ACC_PUBLIC, ACC_SUPER
9Constant pool:
10 #1 = Methodref #6.#22 // java/lang/Object."<init>":()V
11 #2 = Class #23 // com/java/study/jvm/JvmHello
12 #3 = Methodref #2.#22 // com/java/study/jvm/JvmHello."<init>":()V
13 #4 = Methodref #2.#24 // com/java/study/jvm/JvmHello.calculate1:(II)I
14 #5 = Methodref #2.#25 // com/java/study/jvm/JvmHello.calculate2:(II)I
15 #6 = Class #26 // java/lang/Object
16 #7 = Utf8 i
17 #8 = Utf8 I
18 #9 = Utf8 ConstantValue
19 #10 = Integer 2020
20 #11 = Utf8 <init>
21 #12 = Utf8 ()V
22 #13 = Utf8 Code
23 #14 = Utf8 LineNumberTable
24 #15 = Utf8 main
25 #16 = Utf8 ([Ljava/lang/String;)V
26 #17 = Utf8 calculate2
27 #18 = Utf8 (II)I
28 #19 = Utf8 calculate1
29 #20 = Utf8 SourceFile
30 #21 = Utf8 JvmHello.java
31 #22 = NameAndType #11:#12 // "<init>":()V
32 #23 = Utf8 com/java/study/jvm/JvmHello
33 #24 = NameAndType #19:#18 // calculate1:(II)I
34 #25 = NameAndType #17:#18 // calculate2:(II)I
35 #26 = Utf8 java/lang/Object
36{
37 public static final int i;
38 descriptor: I
39 flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
40 ConstantValue: int 2020
41
42 public com.java.study.jvm.JvmHello();
43 descriptor: ()V
44 flags: ACC_PUBLIC
45 Code:
46 stack=1, locals=1, args_size=1
47 0: aload_0
48 1: invokespecial #1 // Method java/lang/Object."<init>":()V
49 4: return
50 LineNumberTable:
51 line 7: 0
52
53 public static void main(java.lang.String[]);
54 descriptor: ([Ljava/lang/String;)V
55 flags: ACC_PUBLIC, ACC_STATIC
56 Code:
57 stack=3, locals=6, args_size=1
58 0: new #2 // class com/java/study/jvm/JvmHello
59 3: dup
60 4: invokespecial #3 // Method "<init>":()V
61 7: astore_1
62 8: iconst_1
63 9: istore_2
64 10: iconst_2
65 11: istore_3
66 12: aload_1
67 13: iload_2
68 14: iload_3
69 15: invokespecial #4 // Method calculate1:(II)I
70 18: istore 4
71 20: aload_1
72 21: iload_2
73 22: iload_3
74 23: invokespecial #5 // Method calculate2:(II)I
75 26: istore 5
76 28: return
77 LineNumberTable:
78 line 11: 0
79 line 12: 8
80 line 13: 10
81 line 14: 12
82 line 15: 20
83 line 16: 28
84
85 private int calculate2(int, int);
86 descriptor: (II)I
87 flags: ACC_PRIVATE
88 Code:
89 stack=3, locals=4, args_size=3
90 0: sipush 666
91 3: istore_3
92 4: iload_3
93 5: iload_1
94 6: iload_2
95 7: iadd
96 8: idiv
97 9: ireturn
98 LineNumberTable:
99 line 19: 0
100 line 20: 4
101
102 private int calculate1(int, int);
103 descriptor: (II)I
104 flags: ACC_PRIVATE
105 Code:
106 stack=2, locals=3, args_size=3
107 0: iload_1
108 1: iload_2
109 2: iadd
110 3: sipush 2333
111 6: imul
112 7: ireturn
113 LineNumberTable:
114 line 24: 0
115}
116SourceFile: "JvmHello.java"
Анализ байт-кода
Строки 1-8
Описывает основную информацию о классе
- Из какого файла *.java он скомпилирован
- последнее время компиляции
- скомпилированный размер
- Контрольное значение MD5
- Последующая версия Java
- Идентификатор доступа, ACC_PUBLIC буквально означает общедоступный; ACC_SUPER не ясно, что это такое, но это должно быть связано с методом super
Строки 9-35 Постоянный пул
постоянный пул времени выполнения
Давайте сначала проанализируем первую константу, расположенную в строке 10 JVMHello.class, мы обнаружим, что за ней есть связанные элементы, и соберем их вместе.
1 #1 = Methodref #6.#22 // java/lang/Object."<init>":()V
2 #6 = Class #26 // java/lang/Object
3 #11 = Utf8 <init>
4 #12 = Utf8 ()V
5 #22 = NameAndType #11:#12 // "<init>":()V
6 #26 = Utf8 java/lang/Object
Methodref указывает определение метода и содержание комментария справа (указывает, что он состоит из этих строк)
1java/lang/Object."<init>":()V
В этом абзаце можно понять объявление родительского конструктора экземпляра этого класса, который также иллюстрирует, что прямым родительским классом класса JVMHELLO является Object.Этот метод по умолчанию имеет значение V, которое равно Void, без возвращаемого значения.
Точно так же вторая константа находится в строке 12 JVMHello.class.
1 #2 = Class #23 // com/java/study/jvm/JvmHello
2 #3 = Methodref #2.#22 // com/java/study/jvm/JvmHello."<init>":()V
3 #11 = Utf8 <init>
4 #12 = Utf8 ()V
5 #22 = NameAndType #11:#12 // "<init>":()V
6 #23 = Utf8 com/java/study/jvm/JvmHello
Конструктор по умолчанию JvmHello() описан здесь, поскольку новый объект находится позже в методе main(), поэтому здесь он будет инициализирован пулом констант.
Третья константа того же анализа находится в строке 13 JVMHello.class.
1 #2 = Class #23 // com/java/study/jvm/JvmHello
2 #4 = Methodref #2.#24 // com/java/study/jvm/JvmHello.calculate1:(II)I
3 #18 = Utf8 (II)I
4 #19 = Utf8 calculate1
5 #24 = NameAndType #19:#18 // calculate1:(II)I
Здесь описано определение метода calculate1 в классе JvmHello.
1 com/java/study/jvm/JvmHello.calculate1:(II)I
(II)Указывает, что входные параметры имеют два основных типа: int
(II)Iтот, что справаIУказывает, что возвращаемое значение также является примитивным типом int.
Это означает, что входными параметрами метода calculate1 являются два целых числа, а возвращаемым значением является целое число.
Затем таким же образом переменная, расположенная в строке 14 JVMHello.class, указывает, что входные параметры метода calculate2 также являются двумя целыми числами, и возвращаемое значение также является целым числом.
Выше приведен анализ информации о пуле констант во время выполнения. Пул констант используется для хранения различных литералов и символических ссылок, сгенерированных во время компиляции. Пул констант делится наобласть методаВнутри этого,область методаИспользуется для хранения данных, констант, статических переменных, компилятора мгновенного компилятора и т. д.
Линия 36-115 Описание внутреннего метода класса
Коллекция таблиц методов
Строка 36-41 Определение статической константы i
Сначала взгляните на определение статических констант, расположенное в JVMHello.class.
1 public static final int i;
2 descriptor: I
3 flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
4 ConstantValue: int 2020
- объявляет общедоступную переменную i типа int
- Возвращаемое значение — целое
- Идентификатор доступа общедоступный, статический, окончательный
- Постоянное значение 2020
Строка 42-52 Определение конструктора класса
1 public com.java.study.jvm.JvmHello();
2 descriptor: ()V
3 flags: ACC_PUBLIC
4 Code:
5 stack=1, locals=1, args_size=1
6 0: aload_0
7 1: invokespecial #1 // Method java/lang/Object."<init>":()V
8 4: return
9 LineNumberTable:
10 line 7: 0
- ()VОбратитесь к определению и описанию предыдущего метода.Вот метод с нулевыми параметрами, V означает, что специальный тип void не имеет возвращаемого значения.
- ACC_PUBLICобщедоступный идентификатор доступа
- stackМаксимальный стек операндов JVM будет выделять глубину стека операций стека кадров в соответствии с этим значением, здесь 1
- localsМесто для хранения, необходимое для локальных переменных, единичный слот, 1 слот = 4 байта, то есть 4 байта.
- args_sizeКоличество параметров метода здесь равно 1, потому что каждый метод экземпляра будет иметь скрытый параметр.
- aload_00 в нем означает слот 0 в таблице локальных переменных. Это означает поместить в стек операндов вещь из слота 0 в таблице локальных переменных.
- invokespecial #1 invokespecialУказывает, что метод экземпляра вызывается на основе типа времени компиляции.#1Указывает на выполнение метода экземпляра, определенного в пуле констант, а именно JvmHello();
- returnВозврат из метода, возвращаемое значение недействительно
- LineNumberTableФункция этого атрибута — описать соответствие между номером строки исходного кода и номером строки байт-кода (смещение байт-кода).
Здесь у нас другая концепциякучаВыполнение метода будет выполнять стек из стека
Строки 53-84
основной метод анализа
1 public static void main(java.lang.String[]);// main方法
2 descriptor: ([Ljava/lang/String;)V // 入参String[],出参V(void)
3 flags: ACC_PUBLIC, ACC_STATIC // 公共的、静态的
4 Code:
5 stack=3, locals=6, args_size=1 // 操作数栈3,局部变量6 Slot,参数个数为1
6 0: new #2 // class com/java/study/jvm/JvmHello new对象
7 3: dup // 复制栈顶部一个字长内容
8 4: invokespecial #3 // Method "<init>":()V 执行JvmHello构造器
9 7: astore_1 // 将returnAddress类型(引用类型)存入到局部变量[1]
10 8: iconst_1 // 将int类型常量[1]压入到操作数栈
11 9: istore_2 // 将int类型值存入局部变量[2]
12 10: iconst_2 // 将int类型常量[2]压入到操作数栈
13 11: istore_3 // 将int类型值存入局部变量[3]
14 12: aload_1 // 从局部变量[1]中装载引用类型值
15 13: iload_2 // 从局部变量[2]中装载int类型值
16 14: iload_3 // 从局部变量[3]中装载int类型值
17 15: invokespecial #4 // Method calculate1:(II)I 执行calculate1方法
18 18: istore 4 // 将int类型值存入局部变量[4]
19 20: aload_1 // 从局部变量[1]中装载引用类型值
20 21: iload_2 // 从局部变量[2]中装载int类型值
21 22: iload_3 // 从局部变量[3]中装载int类型值
22 23: invokespecial #5 // Method calculate2:(II)I 执行calculate2方法
23 26: istore 5 // 将int类型值存入局部变量[5]
24 28: return // void返回
25 LineNumberTable:
26 line 11: 0
27 line 12: 8
28 line 13: 10
29 line 14: 12
30 line 15: 20
31 line 16: 28
Начиная с первой строки нового объекта
1 JvmHello jvmHello = new JvmHello();
2 // 这里的jvmHello就是局部变量[1];
Итак, где находится новый объект, учащиеся, которые видели контент, связанный с jvm, знают, что объект размещен вкучавнутри
окучаОпределение
Для большинства приложений куча Java является самой большой частью памяти, управляемой виртуальной машиной Java. Куча Java — это область памяти, совместно используемая всеми потоками, которая создается при запуске виртуальной машины. Единственной целью этой области памяти является хранение экземпляров объектов, и почти все экземпляры объектов выделяют здесь память. Этот момент описан в спецификации виртуальной машины Java: все экземпляры объектов и массивы должны размещаться в куче, но с развитием JIT-компиляторов и постепенной зрелостью технологии escape-анализа, выделение стека и оптимизация скалярной замены вызовут некоторые трудности. происходили тонкие изменения, все объекты, размещенные в куче, постепенно становились менее «абсолютными».
Затем посмотрите на следующие две строки
1 int a = 1;
2 int b = 2;
3 // 1.这里先将常量[1] = 1压入到操作数栈
4 // 2.再将整个常量[1]的int类型的值赋值给 局部变量[2]也就是 a = 1;
5 // 同理 b=2也是同样的过程
Затем посмотрите на реализацию методов calculate1, calculate2.
1int c = jvmHello.calculate1(a, b);
2int d = jvmHello.calculate2(a, b);
3// 1.从局部变量[1]中装载引用类型值 即jvmHello的值
4// 2.从局部变量[2]中装载int类型值 即值为2
5// 3.从局部变量[3]中装载int类型值 即值为2
6// 4.使用jvmHello执行calculate1方法
7// 同理 calculate2执行过程类似
В приведенном выше абзаце мы знаем, что когда jvm выполняет код, он основан накучареализация, то естьстек операцийВ каждом стеке есть локальные переменные, а локальные переменные размещены втаблица локальных переменныхв
Что касается определения стека Java, у него есть два стека: стек виртуальной машины Java и стек собственных методов.
Стек виртуальной машины Java(стеки виртуальных машин Java) также являются потокозависимыми и имеют тот же жизненный цикл, что и потоки. Стек виртуальной машины описывает модель памяти выполнения метода Java: каждый метод создает кадр стека (Stack Frame) для хранения таблицы локальных переменных, стека операндов, динамической ссылки, выхода метода и другой информации при его выполнении. Процесс каждого метода от вызова до завершения выполнения соответствует процессу помещения кадра стека в стек в стеке виртуальной машины.
собственный стек методовРоль, которую играют (нативный стек методов) и стек виртуальной машины, очень похожа, разница между ними заключается в том, что стек виртуальной машины служит для виртуальной машины для выполнения методов Java (то есть байт-кода), в то время как собственный стек методов — это виртуальная машина. Служба собственного метода, используемая машиной
теперь, когдастек виртуальных машинВ нем упоминается поток, так что вот как его представитьсчетчик команд
Регистр счетчика программ — это небольшое пространство памяти, которое можно рассматривать как индикатор номера строки байт-кода, выполняемого текущим потоком. В концептуальной модели виртуальной машины (только концептуальная модель, различные виртуальные машины могут быть реализованы более эффективными способами) интерпретатор байт-кода работает, изменяя значение этого счетчика для выбора следующего элемента для выполнения Инструкции байт-кода, ветвь , цикл, переход, обработка исключений, восстановление потока и другие базовые функции должны полагаться на этот счетчик для выполнения.
понялкучаа такжекуча, продолжайте добавлять к нашей диаграмме
Продолжим анализ calculate1 и calculate2
1private int calculate2(int, int);
2 descriptor: (II)I // 入参2个int类型 出参int类型
3 flags: ACC_PRIVATE
4 Code:
5 stack=3, locals=4, args_size=3 // 操作数栈3,局部变量4 Slot,参数个数3个
6 0: sipush 666 // 将16位带符号整数(这里指666)压入栈
7 3: istore_3 // 将int类型值(即666)存入局部变量[3]
8 4: iload_3 // 从局部变量[3]中装载int类型值
9 5: iload_1 // 从局部变量[1]中装载int类型值
10 6: iload_2 // 从局部变量[3]中装载int类型值
11 7: iadd // 执行int类型的加法,即 1+2
12 8: idiv // 执行int类型的除法,即 666/3
13 9: ireturn // 返回int类型的值
14 LineNumberTable:
15 line 19: 0
16 line 20: 4
17
18 private int calculate1(int, int);
19 descriptor: (II)I // 入参2个int类型 出参int类型
20 flags: ACC_PRIVATE // 私有的
21 Code:
22 stack=2, locals=3, args_size=3 // 操作数栈2,局部变量3 Slot,参数个数3个
23 0: iload_1 // 从局部变量[1]中装载int类型值
24 1: iload_2 // 从局部变量[2]中装载int类型值
25 2: iadd // 执行int类型的加法,即 1+2
26 3: sipush 2333 // 将16位带符号整数(这里指2333)压入栈
27 6: imul // 执行int类型的乘法 3*2333
28 7: ireturn // 返回int类型的值
29 LineNumberTable:
30 line 24: 0
На самом деле у меня тут вопрос.Почему входных параметров для calculate1 и calculate2 всего 2, а после декомпиляции выводится 3? Я искал.
Оказывается, при вычислении args_size есть способ судить, является ли метод статическим методом, если это не статический метод, то к числу исходных параметров метода будет добавлен один, потому что не- статический метод добавит параметр по умолчанию в начало списка параметров: настоящий исполнитель метода — это экземпляр объекта класса, к которому принадлежит метод. Тогда параметр, соответствующий нашему дополнительному элементу, — это jvmHello.
Наконец, о процессе работы со стеком, здесь я беру calculate1 в качестве примера.
Также упоминалась концепция стека виртуальной машины, упомянутая выше.При выполнении метода будет создан кадр стека для хранения таблицы локальных переменных, стека операндов, динамической ссылки и выхода метода, поэтому на приведенном выше рисунке показан стек. фрейм запихивается в виртуальную машину.К процессу извлечения из стека.На основании этого окончательно добавляем информационный контент в стек
116 строк
Представляет исходный файл JvmHello.java.
Техническое резюме
Анализируя байт-код, вы можете углубить понимание структуры памяти виртуальной машины, всего процесса java-кода от компиляции до загрузки и запуска, вместо того, чтобы запоминать эти концепции в мертвой книге.
Ссылаться на
- Глубокое понимание виртуальной машины Java - Чжоу Чжимин
- Официальная документация по набору инструкций
- Знаете ли вы, сколько параметров может определять метод Java?
END
Если вам это нравится, не забудьте подключиться одной кнопкой
оригинальный мерзавец