程式設計師:JVM虛擬機-棧執行原理深入詳解

程序猿的內心獨白 發佈 2020-01-13T20:19:05+00:00

Compiledfrom "App.java"public com.App { public com.AppV 4: aload_0 5: new #2 // class java/lang/Object 8: dup 9: invokespecial #1 // Method

什麼是JVM

這個大家都應該很熟悉了吧,JVM不就是虛擬機嗎?

Java虛擬機本質上就是一個程序,當它在命令行上啟動的時候,就開始執行保存在某字節碼文件中的指令。

JVM可以說離我們既熟悉又陌生,很多朋友可能在工作中接觸不到這塊技術,但是在面試往往被問到(機率還蠻大),被問到了自認倒霉,死記硬背是沒用的,到頭來還是的忘,今天給大家說道說道JVM知識點,我要沒讓你明白算我輸,你可以留言噴我,如果要是可以,你們也給我點個讚成不?


初識JVM

相信這張圖大家都不陌生,這是整個Java體系,其中包括JDK.JRE.JVM三者的關係。

圖中可以看得出來JRE包含了JVM,JDK包含了JRE。

從包含的角度就是: JDK是爺爺 JRE是父親 JVM是兒子(如果覺得列子不太恰當)來看圖

我們來看代碼:

public class App {

private String name;

private Object object = new Object();

/***

* 運算

*/

public int add() {

int a = 1;

int b = 2;

int c = (a + b) * 100;

return c;

}

/**

* 程序入口

*/

public static void main(String[] args) throws InterruptedException {

App app = new App();

int result = app.add();

System.out.println(result);

}

}

我們運行上述代碼輸出結果是300,雖然這個代碼非常簡單,這個時候已經涉及到JVM相關的知識了,在我們學Java基礎的時候老師就告訴我們,Java是跨平台的,一次編寫到處運行。

那Java是怎麼做到跨平台的?繼續看下圖:

通過此圖大家就不難發現,我們編譯的App.class文件可以在Windows作業系統運行也可以在Linux系統運行,但是兩個系統底層的操作指令是不一樣的,為了屏蔽底層指令的細節,起到一個跨平台的作用,JVM功不可沒,我們常說Java是跨平台還不如說是Jvm跨平台(JRE運行時跨平台)。那Jvm虛擬機是怎麼跨平台的?

JVM底層原理

JVM底層由三個系統構成分別是:類加載、運行時數據區、執行引擎。

我們今天重點講解JVM運行時數據區(棧),其他兩塊可以關注我之前和後續文章。

我們App.class文件通過類加載子系統從硬碟中讀取文件加載到內存中(運行時數據區)。

加載完成之後怎麼處理了?(打個比喻 人吃飯 》吃到肚子裡》各各器官負責自己工作吸收)

Stack棧

先講一下其中的一塊內存區域虛擬機棧,大家都知道棧是數據結構,也是線程獨有的區域,也就是每一個線程都會有自己獨立的棧區域。我們運行App.java輸出300就靠線程執行得來的結果。是哪個線程執行的?獲取線程快照:「main線程」

棧》數據結構》存儲內容》先進後出FILO

大家都知道每個方法都有自己的局部變量,比如上圖中main方法中的result,add方法中的a b c,那麼java虛擬機為了區分不同方法中局部變量作用域範圍的內存區域,每個方法在運行的時候都會分配一塊獨立的棧幀內存區域,我們試著按上圖中的程序來簡單畫一下代碼執行的內存活動。

執行main方法中的第一行代碼是,棧中會分配main()方法的棧幀,並存儲math局部變量,,接著執行add()方法,那麼棧又會分配add()的棧幀區域。

這裡的棧存儲數據的方式和數據結構中學習的棧是一樣的,先進後出。當add()方法執行完之後,就會出棧被釋放,也就符合先進後出的特點,後調用的方法先出棧。

棧幀

棧幀內部「數據結構」主要由這幾個部分組成:局部變量表、操作數棧、方法出口等信息。

說了半天,棧幀到底幹嘛用的呀?別急講這個就會涉及到更底層的原理–字節碼。我們先看下我們上面代碼的字節碼文件。

APP.class文件看著像亂碼,其實每個都是有對應的含義的,oracle官方是有專門的jvm字節碼指令手冊來查詢每組指令對應的含義的。那我們研究的,當然不是這個。

jdk有自帶一個javap的命令,可以將上述class文件生成一種更可讀的字節碼文件。

Compiled from "App.java"

public com.App {

public com.App();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."<init>":()V

4: aload_0

5: new #2 // class java/lang/Object

8: dup

9: invokespecial #1 // Method java/lang/Object."<init>":()V

12: putfield #3 // Field object:Ljava/lang/Object;

15: return

public int add();

Code:

0: iconst_1

1: istore_1

2: iconst_2

3: istore_2

4: iload_1

5: iload_2

6: iadd

7: bipush 100

9: imul

10: istore_3

11: iload_3

12: ireturn

public static void main(java.lang.String[]) throws java.lang.InterruptedException;

Code:

0: new #4 // class com/App

3: dup

4: invokespecial #5 // Method "<init>":()V

7: astore_1

8: aload_1

9: invokevirtual #6 // Method add:()I

12: istore_2

13: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;

16: iload_2

17: invokevirtual #8 // Method java/io/PrintStream.println:(I)V

20: return

}


此時的jvm指令碼就清晰很多了,大體結構是可以看懂的,類、靜態變量、構造方法、add()方法、main()方法。

其中方法中的指令還是有點懵,我們舉add()方法來看一下:

Code:

0: iconst_1

1: istore_1

2: iconst_2

3: istore_2

4: iload_1

5: iload_2

6: iadd

7: bipush 100

9: imul

10: istore_3

11: iload_3

12: iretu


這幾行代碼就是對應的我們代碼中add()方法中的四行代碼。大家都知道越底層的代碼,代碼實現的行數越多,因為他會包含一些java代碼在運行時底層隱藏的一些細節原理。

那麼一樣的,這個jvm指令官方也是有手冊可以查閱的,網上也有很多翻譯版本,大家如果想了解可自行百度。

執行流程

設計代碼中的部分指令含義:

第一步:壓棧

將int類型常量1壓入操作數棧

0: iconst_1

就是將1壓入操作數棧

更正:執行流程過程中灰色背景「操作數棧」應改為「局部變量表」(!!!!!!!!!!)。

第二步:存儲

將int類型值存入局部變量1

1: istore_1

局部變量1,在我們代碼中也就是第一個局部變量a,先給a在局部變量表中分配內存,然後將int類型的值,也就是目前唯一的一個1存入局部變量a

第三步:賦值

這兩行代碼就和前兩行類似了。

2: iconst_2

3: istore_2

第四步:裝載

從局部變量2中裝載int類型值

4: iload_1

5: iload_2

這兩個代碼是將局部變量1和2,也就是a和b的值裝載到操作數棧中

第五步:加法

執行int類型的加法

6: iadd

iadd指令一執行,會將操作數棧中的1和2依次從棧底彈出並相加,然後把運算結果3在壓入操作數棧底。

第六步:壓棧

將一個8位帶符號整數壓入棧

7: bipush 100

這個指令就是將100壓入棧

第七步:乘法

執行int類型的乘法

9: imul

這裡就類似上面的加法了,將3和100彈出棧,把結果300壓入棧

第八步:壓棧

將將int類型值存入局部變量3

10: istore_3

這裡大家就不陌生了吧,和第二步第三步是一樣的,將300存入局部變量3,也就是c

第九步:裝載

從局部變量3中裝載int類型值

11: iload_3

從局表變量3加載到操作數棧

第十步:返回

返回int類型值

12: ireturn

我們add方法是被main方法中調用的,所以通過方法出口返回到mian方法中result變量存儲方法出口說白了不就是方法執行完了之後要出到哪裡,那麼我們知道上面add()方法執行完之後應該回到main()方法第三行那麼當main()方法調用add()的時候,add()棧幀中的方法出口就存儲了當前要回到的位置,那麼當add()方法執行完之後,會根據方法出口中存儲的相關信息回到main()方法的相應位置。看我圖中的紅線

棧堆關係

main方法中除了result變量還有一個app變量,app變量指向的是一個對象。那對象是怎麼存儲的?這兒要在說下局表變量表結構:基本類型和引用類型(Java叫引用C C++叫指針)

關係就是:

通過引用在棧中的app變量引用堆中的App對象

總結

講到這兒相信大家對JVM棧執行原理是不是熟悉了?如果覺得不錯歡迎點讚評論。

關鍵字: