[серия базовых навыков java] jvm основные принципы jvm без механического запоминания

Java

предисловие

Для понимания основных принципов лучше всего использовать практический опыт;

когда начать

пример

 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

Помните, что я упоминал в предыдущей книге, скомпилируйте один раз и выполните везде, затем сначала файл должен быть загружен и запущен в среде; таким образом, у нас есть предварительная диаграмма

image-20200115172146495
image-20200115172146495

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 также являются двумя целыми числами, и возвращаемое значение также является целым числом.

Выше приведен анализ информации о пуле констант во время выполнения. Пул констант используется для хранения различных литералов и символических ссылок, сгенерированных во время компиляции. Пул констант делится наобласть методаВнутри этого,область методаИспользуется для хранения данных, констант, статических переменных, компилятора мгновенного компилятора и т. д.

image-20200116164521074
image-20200116164521074

Линия 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 (то есть байт-кода), в то время как собственный стек методов — это виртуальная машина. Служба собственного метода, используемая машиной

теперь, когдастек виртуальных машинВ нем упоминается поток, так что вот как его представитьсчетчик команд

Регистр счетчика программ — это небольшое пространство памяти, которое можно рассматривать как индикатор номера строки байт-кода, выполняемого текущим потоком. В концептуальной модели виртуальной машины (только концептуальная модель, различные виртуальные машины могут быть реализованы более эффективными способами) интерпретатор байт-кода работает, изменяя значение этого счетчика для выбора следующего элемента для выполнения Инструкции байт-кода, ветвь , цикл, переход, обработка исключений, восстановление потока и другие базовые функции должны полагаться на этот счетчик для выполнения.

понялкучаа такжекуча, продолжайте добавлять к нашей диаграмме

image-20200116170901654
image-20200116170901654

Продолжим анализ 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 в качестве примера.

image-20200116161136903
image-20200116161136903

Также упоминалась концепция стека виртуальной машины, упомянутая выше.При выполнении метода будет создан кадр стека для хранения таблицы локальных переменных, стека операндов, динамической ссылки и выхода метода, поэтому на приведенном выше рисунке показан стек. фрейм запихивается в виртуальную машину.К процессу извлечения из стека.На основании этого окончательно добавляем информационный контент в стек

image-20200116172500427
image-20200116172500427

116 строк

Представляет исходный файл JvmHello.java.

Техническое резюме

Анализируя байт-код, вы можете углубить понимание структуры памяти виртуальной машины, всего процесса java-кода от компиляции до загрузки и запуска, вместо того, чтобы запоминать эти концепции в мертвой книге.

Ссылаться на

END

Если вам это нравится, не забудьте подключиться одной кнопкой
оригинальный мерзавец