8張圖 帶你理解Java內(nèi)存區(qū)域

很多人會誤以為Java內(nèi)存區(qū)域和內(nèi)存模型是同一個(gè)東西,其實(shí)并不是。

Java內(nèi)存區(qū)域是指 JVM運(yùn)行時(shí)將數(shù)據(jù)分區(qū)域存儲 ,簡單的說就是不同的數(shù)據(jù)放在不同的地方。通常又叫 運(yùn)行時(shí)數(shù)據(jù)區(qū)域

Java內(nèi)存模型(JMM)定義了程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。

1、Java內(nèi)存區(qū)域

1.8 之前:
Java內(nèi)存區(qū)域 1.8之前
JDK1.8(含)之后:
Java內(nèi)存區(qū)域 1.8

區(qū)別就是 1.8有一個(gè)元數(shù)據(jù)區(qū)替代方法區(qū)了。

JDK 1.7 其實(shí)是并沒完全移除方法區(qū),但是不會像1.6以前報(bào) “java.lang.OutOfMemoryError: PermGen space”,而是報(bào) java.lang.OutOfMemoryError: Java heap space

1.7部分內(nèi)容(比如 常量池、靜態(tài)變量有方法區(qū)轉(zhuǎn)移到了堆)

演變

那么,Java 8 中 PermGen 為什么被移出 HotSpot JVM 了?我總結(jié)了兩個(gè)主要原因(詳見:JEP 122: Remove the Permanent Generation):

  1. 由于 PermGen 內(nèi)存經(jīng)常會溢出,引發(fā)惱人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的開發(fā)者希望這一塊內(nèi)存可以更靈活地被管理,不要再經(jīng)常出現(xiàn)這樣的 OOM
  2. 移除 PermGen 可以促進(jìn) HotSpot JVM 與 JRockit VM 的融合,因?yàn)?JRockit 沒有永久代。

根據(jù)上面的各種原因,PermGen 最終被移除,方法區(qū)移至 Metaspace,字符串常量移至 Java Heap。

引用自https://www.sczyh30.com/posts/Java/jvm-metaspace/

下面逐一介紹一下jvm管轄的這幾種內(nèi)存區(qū)域。

2、程序計(jì)數(shù)器

程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,由于JVM可以并發(fā)執(zhí)行線程,因此會存在線程之間的切換,而這個(gè)時(shí)候就程序計(jì)數(shù)器會記錄下當(dāng)前程序執(zhí)行到的位置,以便在其他線程執(zhí)行完畢后,恢復(fù)現(xiàn)場繼續(xù)執(zhí)行。

JVM會為每個(gè)線程分配一個(gè)程序計(jì)數(shù)器,與線程的生命周期相同。

如果線程正在執(zhí)行的是應(yīng)該Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行虛擬機(jī)字節(jié)碼指令的地址。

如果正在執(zhí)行的是Native方法,計(jì)數(shù)器的值則為空(undefined)

程序計(jì)數(shù)器是唯一一個(gè)在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域。

3、Java虛擬機(jī)棧

虛擬機(jī)棧 描述的是 Java 方法執(zhí)行的內(nèi)存模型:

每個(gè)方法在執(zhí)行的同時(shí)都會創(chuàng)建一個(gè)棧幀(Stack Frame,是方法運(yùn)行時(shí)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu))用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。

虛擬機(jī)棧是每個(gè)線程獨(dú)有的,隨著線程的創(chuàng)建而存在,線程結(jié)束而死亡。

在虛擬機(jī)棧內(nèi)存不夠的時(shí)候會OutOfMemoryError,在線程運(yùn)行中需要更大的虛擬機(jī)棧時(shí)會出現(xiàn)StackOverFlowError

虛擬機(jī)棧包含很多棧幀,每個(gè)方法執(zhí)行的同時(shí)會創(chuàng)建一個(gè)棧幀,棧幀又存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址等信息。

在活動線程中,只有位于棧頂?shù)臈攀怯行У?,稱為當(dāng)前棧幀,與這個(gè)棧幀相關(guān)聯(lián)的方法稱為當(dāng)前方法。

1)局部變量表

局部變量表是存放方法參數(shù)局部變量的區(qū)域。

全局變量是放在堆的,有兩次賦值的階段,一次在類加載的準(zhǔn)備階段,賦予系統(tǒng)初始值;另外一次在類加載的初始化階段,賦予代碼定義的初始值。

而局部變量沒有賦初始值是不能使用的。

2)操作數(shù)棧

一個(gè)先入后出的棧。

當(dāng)一個(gè)方法剛剛開始執(zhí)行的時(shí)候,這個(gè)方法的操作數(shù)棧是空的,在方法的執(zhí)行過程中,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容,也就是出棧/入棧操作。

3) 動態(tài)連接

每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用。持有這個(gè)引用是為了支持方法調(diào)用過程中的動態(tài)連接(Dynamic Linking)。

常量池可以便于指令的識別

    public void methodA(){
        
    }
    public void methodB(){
        methodA();//methodB()調(diào)用methodA(),先找到調(diào)用methodA()的版本符號,再變?yōu)橹苯右?    }

方法調(diào)用并不等同于方法執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本(即調(diào)用哪一個(gè)方法),這也是Java強(qiáng)大的擴(kuò)展能力,在運(yùn)行期間才能確定目標(biāo)方法的直接引用。

所有方法調(diào)用中的目標(biāo)方法在Class文件里面都是一個(gè)常量池中的符號引用,在類加載的解析階段,會將其中的一部分符號引用轉(zhuǎn)化為直接引用。

4)方法返回地址(方法出口)

返回分為 正常返回 和 異常退出。

無論何種退出情況,都將返回至方法當(dāng)前被調(diào)用的位置,這也程序才能繼續(xù)執(zhí)行。

一般來說,方法正常退出時(shí),調(diào)用者的PC計(jì)數(shù)器的值可以作為返回地址,棧幀中會保存這個(gè)計(jì)數(shù)器值。

方法退出的過程相當(dāng)于彈出當(dāng)前棧幀。

4、本地方法棧

Java虛擬機(jī)棧是調(diào)用Java方法;本地方法棧是調(diào)用本地native方法,可以認(rèn)為是通過 JNI (Java Native Interface) 直接調(diào)用本地 C/C++ 庫,不受JVM控制。

Native方法
Java虛擬機(jī)棧與本地方法棧的調(diào)用過程

本地方法棧也會拋出 StackOverflowErrorOutOfMemoryError 異常

5、Java堆

Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存。

堆是垃圾收集器管理的主要區(qū)域,又稱為“GC堆”,可以說是Java虛擬機(jī)管理的內(nèi)存中最大的一塊。

現(xiàn)在的虛擬機(jī)(包括HotSpot VM)都是采用分代回收算法。在分代回收的思想中, 把堆分為:新生代+老年代+永久代(1.8沒有了); 新生代 又分為 Eden + From Survivor + To Survivor區(qū)。

6、方法區(qū)

方法區(qū)(Method Area)與 Java 堆一樣,是所有線程共享的內(nèi)存區(qū)域。

方法區(qū)用于存儲已經(jīng)被虛擬機(jī)加載的類信息(即加載類時(shí)需要加載的信息,包括版本、field、方法、接口等信息)、final常量、靜態(tài)變量、編譯器即時(shí)編譯的代碼等。

方法區(qū)邏輯上屬于堆的一部分,但是為了與堆進(jìn)行區(qū)分,通常又叫“非堆”。

方法區(qū)比較重要的一部分是運(yùn)行時(shí)常量池(Runtime Constant Pool),為什么叫運(yùn)行時(shí)常量池呢?是因?yàn)檫\(yùn)行期間可能會把新的常量放入池中,比如說常見的String的intern()方法。

String a = "I am HaC";
Integer b = 100;

在編譯階段就把所有的字符串文字放到一個(gè)常量池中,復(fù)用同一個(gè)(比如說上述的“I am HaC”),節(jié)省空間。

關(guān)于方法區(qū)和元空間的關(guān)系:

方法區(qū)是JVM規(guī)范概念,而永久代則是Hotspot虛擬機(jī)特有的概念,簡單點(diǎn)理解:方法區(qū)和堆內(nèi)存的永久代其實(shí)一個(gè)東西,但是方法區(qū)是包含了永久代。

只有 HotSpot 才有 “PermGen space”,而對于其他類型的虛擬機(jī),如 JRockit(Oracle)、J9(IBM) 并沒有“PermGen space”

7、元空間

1.8就把方法區(qū)改用元空間了。類的元信息被存儲在元空間中。元空間沒有使用堆內(nèi)存,而是與堆不相連的本地內(nèi)存區(qū)域。所以,理論上系統(tǒng)可以使用的內(nèi)存有多大,元空間就有多大,所以不會出現(xiàn)永久代存在時(shí)的內(nèi)存溢出問題。

可以通過 -XX:MetaspaceSize-XX:MaxMetaspaceSize 來指定元空間的大小。

8、總結(jié):

Java內(nèi)存區(qū)域

參考:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容