終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

喝水會長肉 發表於 2021-12-07
Java

 不要再把Java記憶體區域和Java記憶體模型混淆,再問就自閉啦。😏

小橙出品 必屬精品。先贊後看 養成習慣 。

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

上次我們講了JVM的 類載入機制,主要涉及了 雙親委派機制如何破壞雙親委派機制,不明白的小夥伴可以移步上篇 不要再死記硬背Java的類載入啦

網上有很多關於Java區域和記憶體模型的文章,但是很多人讀完之後還是搞不清楚,主要是因為大家把這兩個混為一談,也不關心JDK版本。所以概念一多,非常容易混淆。

所以下面將重點說明Java記憶體區域,主要涉及 執行時的幾大區域劃分每個區域的職責。而Java記憶體模型的文章可以參考我之前寫的 [併發基礎篇]MESI協議,JMM,執行緒常見方法等

一、簡介

我們都知道作為計算機界的老大哥C語言,其是面向記憶體進行開發的。對於每個物件,我們都需要手動的建立,開闢記憶體,銷燬,如果不銷燬,他可能永遠刪除不了啦。

但是Java並不是這樣的,其有一整套虛擬機器自動記憶體管理機制,我們不需要手動的建立物件,開闢記憶體,乃至銷燬,不易發生記憶體溢位和洩露。這樣看上去是不是爽歪歪。

但是這也是頭疼的,對於其內部,我們是完全懵逼的狀態。一旦出現問題,完全不知道咋整。

所以我們接下來,就要學習Java記憶體到底是如何工作的,各部分如何劃分。

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)


二、整體框架

首先我們JVM會載入類到記憶體中,所以 JVM 中必然會有一塊記憶體區域來存放我們寫的那些類。Java中有類物件、普通物件、本地變數、方法資訊等等各種物件資訊,所以JVM會對記憶體區域進行劃分:

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

三、方法區

方法區與堆一樣,也是執行緒共享的記憶體區域,主要用來存放類的資訊。

方法區只存在於JDK1.8以前的版本,主要是儲存從”.class“檔案里載入進來的類,包括類的名稱、方法資訊、欄位資訊、靜態變數、常量以及編譯器編譯後的程式碼等。其也被稱為“永久代”,但是這兩者並不等價,只是垃圾收集器的分代收集理論擴充套件到了方法區,使得垃圾收集器能夠像管理Java堆一樣管理記憶體,省去了專門為方法區編寫程式碼的工作。

注意:這只是對於HotSpot的虛擬機器有用,對於其他的虛擬機器,如IBM J9,是不存在永久代的概念的。

而且其有大小限制,我們可以設定-XX:MaxPermSize的大小來控制方法區的上限。但是其有利有弊。有利的是我們更容易把控程式啟動後所佔據的記憶體,有弊的是一旦超出了限制,就會出現java.lang.OutOfMemoryError: PermGen報錯。(此處可考面試題,線上調優經驗,注意注意。)

從JDK1.8開始,這塊區域的名字改成了後設資料區(Metaspace),後設資料區直接使用本地記憶體。比1.8之前的版本對比,其直接向記憶體申請,不會再出現之前記憶體不夠的情況啦。如果不夠,那就要加伺服器配置的事情啦。但也不能無限擴充套件,因此可以使用 -XX:MaxMetaspaceSize來控制最大記憶體。

舉個例子:


public 
class 
Test

{
    public static void main ( String [ ] args )
    {
    }
}

我們新建了一個Test類,裡面有個空的main方法,當通過類載入,其類相關的資訊就會被載入到方法區中,如下圖。

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

四、程式計數器

其是很小的一塊記憶體空間,可以看作是當前執行緒所執行的位元組碼的行號。因為Java虛擬機器的多執行緒時通過執行緒輪流切換,輪流得到處理器的資源來進行的,所以就需要線上程中有一個計數器來記錄執行到哪一行,方便切換回來,所以每個執行緒都需要一個獨立的程式計數器,他們相互獨立,互不影響。

白話解釋:

這個大冬天的,正躺著愉快刷劇,突然餓了,要去泡個泡麵吃吃,這時候是不是要暫停,先泡個面?等泡麵好了,再回來繼續看。那麼這邊的暫停就相等於程式計數器。

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

在上面例子的基礎,我們多加兩行程式碼:


public 
class 
Test

{
    public static void main ( String [ ] args )
    {
//java學習交流:737251827 進入可領取學習資源及對十年開發經驗大佬提問,免費解答!

        A a = new A ( ) ;
       a . load ( ) ;
    }
}

當JVM載入類資訊到記憶體之後,實際就會使用自己的位元組碼執行引擎,去執行這些位元組碼指令, 程式計數器是執行緒私有的,也就是說每個執行緒都有個自己的程式計數器,記錄當前執行緒執行到了哪一條位元組碼指令。

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

五、Java虛擬機器棧

Java虛擬機器棧,其實是一種描述Java方法執行的資料結構。每個方法被執行的時候,都會建立一個棧幀(Stack Frame)用於儲存區域性變數表、操作棧、動作連結、方法出口等資訊。

每個方法從被呼叫到執行完成的過程,其實就是一個棧幀在虛擬機器棧中從入棧到出棧的過程。

比如main執行緒執行了main()方法,那麼就會建立一個棧幀(裡面存放a區域性變數),並將其壓入main執行緒自己的Java虛擬機器棧中,如下圖:  

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

然後main執行緒繼續執行load方法,再執行A類中的方法,其如果有區域性變數,就會再建立一個棧幀,壓入自己的虛擬機器棧中,如下圖。

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

六、本地方法棧

本地方法棧,其作用和Java虛擬機器棧類似,區別在於本地方法棧是為虛擬機器所使用到的Native方法服務,而Java虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務。本地方法棧也是執行緒私有的。

 JDK中的很多底層API,比如IO、NIO、網路等,如果大家去看它的原始碼,會發現很多地方是呼叫的native修飾的方法,如CAS,這塊先暫且擱下,後面併發說。

比如下面這樣:

public native int hashCode();

在呼叫native方法時,也會有執行緒對應的棧來儲存native方法底層用到的區域性變數表之類的資訊,這就是本地方法棧的作用。

七、Java堆

是資料管理中最大的一塊。所有執行緒共享的一塊記憶體內容,在虛擬機器啟動時就已經自動建立的。這一塊主要存放物件,也就是平時經常說的垃圾回收區域。

Java堆記憶體,這是JVM記憶體區域中最重要的一塊區域,存放著各種Java物件,是執行緒共享區域。

下面程式碼中,new A()建立了一個物件例項,這個物件例項的相關資訊就存放在Java堆記憶體中:


public 
class 
Test
{

    public static void main ( String [ ] args ) {
            A a = new A ( ) ;
           a . load ( ) ;
      }
}

main執行緒在執行main()方法時,會為其建立一個棧幀併入棧,棧幀中的區域性變數a存放著A物件例項在Java堆記憶體中的地址:

終於有人把Java記憶體區域說清楚了!(不是記憶體模型,不要再混淆了)

八、總結

該篇主要講了JVM的執行時記憶體區域,主要包括他們各自的劃分,每個部分具體儲存的是什麼,中間結合程式碼詳細說明了過程,希望能給大家一點幫助,不要再以為裡面是個黑匣子,一臉懵逼的狀態。(主要為了面試不懵逼,哈哈哈)

 如果覺得寫得還行,麻煩給個贊👍,您的認可才是我寫作的動力!

 如果覺得有說的不對的地方,歡迎評論指出。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2846304/,如需轉載,請註明出處,否則將追究法律責任。