[java基本功系列]jvm之不須要死記硬背的jvm基本原理

前言

對基本原理的瞭解,動手是最好的;html

哪裏入手

例子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}
複製代碼

這段代碼我就不解釋了 直接編譯字節碼搞起git

1# 編譯生成 JvmHello.class文件
2javac JvmHello.java
3# 反編譯字節碼內容
4javap -verbose -p JvmHello.class
複製代碼

記得以前書裏提到的,編譯一次處處執行,那麼首先文件要被加載進來,運行在一個環境裏面;因此咱們有了初步的圖github

image-20200115172146495
image-20200115172146495

JvmHello.java -> JvmHello.class -> 類裝載系統加載進來 -> 在虛擬機環境執行web

接着咱們看下JVMHello.class的內容shell

 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  flagsACC_PUBLICACC_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         4return
50      LineNumberTable:
51        line 70
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         0new           #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        28return
77      LineNumberTable:
78        line 110
79        line 128
80        line 1310
81        line 1412
82        line 1520
83        line 1628
84
85  private int calculate2(intint);
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 190
100        line 204
101
102  private int calculate1(intint);
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 240
115}
116SourceFile: "JvmHello.java"
複製代碼

字節碼分析

1-8行

描述了類的基本信息數組

  • 它是由哪一個 *.java 文件編譯而成的
  • 最後編譯時間
  • 編譯後的大小
  • MD5校驗值
  • 遵循的java版本
  • 訪問標識,ACC_PUBLIC字面意思公有的嘛;ACC_SUPER不清楚是什麼,可是應該和super方法有關係

9-35行 Constant pool

運行時常量池bash

咱們先分析下第一個常量,位於JVMHello.class第10行,咱們會發現後面有關聯項 一塊兒放進來oracle

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表示方法定義,右側的註釋內容(表示是由這幾行組合起來的)app

1java/lang/Object."<init>":()V
複製代碼

這段能夠理解爲該類的實例父類構造器的聲明,此處也說明了JvmHello類的直接父類是Object.該方法默認返回值是V,也就是void,無返回值

同理分析下第二個常量,位於JVMHello.class第12行

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()方法裏面new了對象 因此這裏會初始化到常量池

同理分析下第三個常量,位於JVMHello.class第13行

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
複製代碼

這裏描述的是JvmHello類裏面calculate1方法的定義

1    com/java/study/jvm/JvmHello.calculate1:(II)I
複製代碼

(II) 表示入參爲兩個基本類型int

(II)I 右邊的這個I表示返回值也是基本類型int

連起來講就是 calculate1方法入參是兩個int,返回值是int

那麼同理可得 位於JVMHello.class第14行的變量表示的是 calculate2方法入參也是兩個int,返回值也是int

上述就是運行時常量池信息的分析,常量池用於存放編譯期生成的各類字面量和符號引用,常量池是被劃分在了方法區這個裏面,方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據到這咱們補充一下咱們的jvm圖

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
  • 返回值爲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         4return
9      LineNumberTable:
10        line 70
複製代碼
  • ()V 參考以前方法的定義描述 這裏是空參的方法;V表示特殊類型void無返回值
  • ACC_PUBLIC 訪問標識公共的
  • stack 最大操做數棧 JVM會根據這個值來分配幀棧的操做棧深度,這裏是1
  • locals 局部變量所需存儲空間,單位Slot,1Slot=4B,那麼這裏就是4個字節
  • args_size方法參數的個數,這裏是1,由於每一個實例方法都會有一個隱藏參數this
  • aload_0 當中的0正是局部變量表裏的Slot 0的含義。意思是將局部變量表裏的Slot 0的東西壓入操做數棧,這個Slot 0裏的東西name正是this,也就是JvmHello的實例
  • invokespecial #1 invokespecial表示根據編譯時類型來調用實例方法 #1表示執行 常量池裏面定義的實例方法,即JvmHello();
  • return 從方法中返回,返回值爲void
  • LineNumberTable 該屬性的做用是描述源碼行號與字節碼行號(字節碼偏移量)之間的對應關係

這裏咱們產生了另外一個概念,方法執行會進行壓棧出棧

53-84行

main方法分析

 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         0new           #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        28return                                                        // void返回
25      LineNumberTable:                                                
26        line 110
27        line 128
28        line 1310
29        line 1412
30        line 1520
31        line 1628
複製代碼

從第一行new對象提及

1  JvmHello jvmHello = new JvmHello();
2  // 這裏的jvmHello就是局部變量[1];
複製代碼

那麼new出來的對象放在哪裏的,看過jvm相關內容的同窗都知道對象是分配在裏面的

關於的定義

對於大多數應用來講,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。這一點在Java虛擬機規範中的描述是:全部的對象實例以及數組都要在堆上分配,可是隨着JIT編譯器的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換 優化技術將會致使一些微妙的變化發生,全部的對象都分配在堆上也漸漸變得不是那麼「絕對」了。

接着看下面兩行

1 int a = 1;
2 int b = 2;
3 // 1.這裏先將常量[1] = 1壓入到操做數棧
4 // 2.再將整個常量[1]的int類型的值賦值給 局部變量[2]也就是 a = 1;
5 // 同理 b=2也是一樣的過程
複製代碼

而後看執行calculate一、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 Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame )用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。

本地方法棧(Native Method Stack)與虛擬機棧所發揮的做用是很是類似的,它們之間的區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務

既然虛擬機棧裏面提到線程,那麼這裏順便介紹下程序計數器

程序計數器(Program Counter Register)是一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏(僅是概念模型,各類虛擬機可能會經過一些更高效的方式去實現),字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。

知道了,繼續補充一下咱們的圖

image-20200116170901654
image-20200116170901654

咱們繼續分析下calculate1和calculate2

 1private int calculate2(intint);
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 190
16        line 204
17
18  private int calculate1(intint);    
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 240
複製代碼

其實到這裏我有個疑問 爲何calculate1和calculate2的入參明明只有2個,反編譯後會顯示2個呢?我去搜了下

原來在計算args_size時,有判斷方法是否爲static方法,若是不是static方法,則會在方法原有參數數量上再加一,這是由於非static方法會添加一個默認參數到參數列表首位:方法的真正執行者,即方法所屬類的實例對象。那對應咱們這多出來的參數就是 jvmHello了

最後關於操做棧的過程 這裏我以calculate1爲例

image-20200116161136903
image-20200116161136903

上面提到的虛擬機棧的概念也提過,方法執行的同時會建立棧幀,存儲局部變量表、操做數棧、動態連接、方法出口;因此上圖就是一個棧幀在虛擬機中入棧到出棧的過程.基於這點最後補充一下棧裏面的信息內容

image-20200116172500427
image-20200116172500427

116行

表示源文件JvmHello.java

技術總結

經過分析字節碼,能夠加深對虛擬機內存結構,java代碼從編譯到加載,和運行的整個過程,而不是去死記書裏的那些概念。

參考

END

喜歡的記得一鍵三連
原文git

相關文章
相關標籤/搜索