一個Java類在運行時候,變量是怎麼在JVM中分佈的呢?

JVM學習第三篇思考:一個Java類在Jvm內存中是怎麼存在的

又名:Java虛擬機的內存模型(JMM)是什麼樣的.

通過前面兩篇文章的學習,我們知道了一個Java類的生命週期及類加載器。我們可以得到如下兩幅圖:

類生命週期:

編輯

父類委託機制:

編輯

思考:

編輯

我們編寫的類中的變量、方法、對象這些都需要內存存放的。那麼在運行時候這些數據在Java虛擬機內存中是怎麼存放的呢?

本文目標:

凱哥(凱哥Java:kaigejava)希望通過本文學習,大家對Java虛擬機運行時數據區域有更深的瞭解

我們寫的代碼在JVM中是怎麼存在的?

1:我們現在看看總體Java運行時數據模型:

編輯

2:我們來看看下面這段代碼,執行的時候,在JVM中數據存放:

編輯

上面代碼很簡單,那麼對應的變量、對象等在內存中都是怎麼分配的呢?

2.1:方法區

注:在JDK1.8之後,方法區被元空間替換了。

方法區:用來存放的是類的信息、常量、靜態變量等。該區域也是各個線程共享的內存區域。

根據Java虛擬機規範中的規定,當方法去無法滿足內存分配的時候,會拋出:OutOfMemoryError異常的。

根據上面的 定義,我們可以知道比如我們JvmDemo.class信息、static string str=“jvmDemo”是在方法區存放的。

對應咱們代碼,方法區存放的如下圖:

編輯

2.2:堆區

堆區是JVM所管理的內存中的最大的一塊區域。該區域是所有線程共享的一塊內存區域。該區域空間在虛擬機啓動的時候就被創建了(-Xms的設置。後面凱哥(凱哥Java:kaigejava)也會詳細講解的)。

此區域的目的是存放對象實例的。幾乎所有的對象實例都是在這裏分配的。Java虛擬機規範中是這麼描述的:所有的對象的實例以及數組都要在堆上分配。

堆區是垃圾收集器管理的主要區域(後面凱哥(凱哥Java:kaigejava)也會詳細講解的).

堆區空間,在物理上可以不是連續的內存空間,只要在邏輯上是連續的即可。如果堆沒有內存完成實例分配,並且堆也無法在擴展的時候,將會拋出異常:OutOfMemoryError。這個大家很熟悉吧。

根據上面定義的,我們可以知道,上面代碼中Son son = new Son(); 這行代碼創建的實例對象是存放在堆區的。

編輯

2.3:程序計數器

程序計數器的作用可以看做是當前線程所執行的字節碼的行號指示器。字節碼解釋器在工作的時候,時候通過改變計數器的值來選擇接下來要執行的字節碼指令的。

同時我們都知道,當多線程的時候,Java虛擬機是通過線程輪流切換分配處理器執行時間的方式來實現的。在任何一個確定的時刻一個處理器只會執行一條線程中的指令。因此,爲了解決多個線程在切換後,能夠迅速恢復到切換前執行的位置,每個線程都需要有個獨立的程序計數器,各個線程直接的計數器互不影響,獨立存儲的。一般稱這類內存區域爲:"線程私有"的內存。當線程正在執行的一個方法是Native的,這種情況下,計數器的值就是undefined了。這個區域也是Java虛擬機內存區域中唯一一個沒有OOM的區域。

根據上面描述,我們可以知道,我們自己編寫的*.java文件要想被執行,需要被編譯成*.class的字節碼文件。字節碼文件對應各種字節碼指令。比如我們上面JvmDemo的字節碼文件:

編輯

從上面截圖,我們可以看到,行號是0,3,4,7,8這樣的。程序計數器就是記錄這些行號的

我們也可以使用idea的插件,來查看我們JvmDemo的相關信息:

編輯

2.4:虛擬機棧

Java虛擬機棧,也是線程私有的。其生命週期與線程相同,當一個線程運行結束後,對應的虛擬機棧也結束。

虛擬機棧是Java方法執行的內存模型:即每個方法被執行的時候,都會被同時創建一個棧幀(Stack Frame),這個棧幀是用來存放方法局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直到其執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧和出棧的過程。

比如:我們上面代碼執行的時候,執行main方法的時候,主線程就會把main方法壓入到虛擬機棧中,當執行到add方法的時候,add方法就被壓入到棧中了。當執行完add方法後,add方法就被從虛擬機棧中彈出,這個時候add對應的棧幀也銷燬。

虛擬機棧幀如下圖:

編輯

局部變量存放:各種基本數據類型、對象引用和返回類型

八大基本數據類型:boolean、byte、char、short、init、float、long、double;

對象引用:reference類型。這裏是存放的是對象在對內存中的地址值。不等同於對象自身的。根據不同的虛擬機的實現,這個指向可能是指向了對象起始地址的引用指針,也有可能是指向了對象對象的句柄或其他對象與其他對象的位置;

返回類型:returnAddress類型。指向一條字節碼指令地址。

擴展:long類型和double類型的數據會佔用2個局部變量空間。其他6個數據類型佔用1個。

局部變量表所消耗的內存空間在編譯期間就完成了分配,當進入一個方法的時候,這個方法需要在棧幀中分配多大的局部變量空間是完全確定的。在方法的運行期間,不會改變該區域空間大小的。

在咱們上面代碼中,虛擬機棧存放的就是咱們main方法和add方法相關的

2.5:本地方法棧

本地方法棧的作用和虛擬機棧的作用相似。不同之處在於:虛擬機棧是爲了虛擬機執行Java方法服務的。而本地方法棧則是爲了虛擬機使用到Native方法服務的。此區域也是方法私有的。比如我們調用線程的run方法或者CAS的時候,調用的都是native方法。

總結:

通過本文學習,我們在自己腦海中應該有如下圖的概念:

編輯

其中方法區、堆區是所有線程共享的;虛擬機棧、程序計數器、本地方法棧這三個是線程私有的,其生命週期同線程一致。

方法區:存放類型、常量、靜態變量等

堆區:用來存放對象實例、數組

虛擬機棧:局部變量表、動態鏈接、操作棧等

本地方法棧:用來存放當線程調用native方法的時候使用的

程序計數器:用來記錄當前線程執行的字節碼行號的。

好了,本文凱哥(凱哥Java:kaigejava)就和大家嘮嘮在運行時候Java虛擬機的數據區域。在下篇文章中,咱們在詳細嘮嘮堆區。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章