JVM內(nèi)存模型
Java虛擬機(jī)(Java Virtual Machine=JVM)的內(nèi)存空間分為五個(gè)部分,分別是:
1. 程序計(jì)數(shù)器
2. Java虛擬機(jī)棧
3. 本地方法棧
4. 堆
5. 方法區(qū)。
下面對(duì)這五個(gè)區(qū)域展開(kāi)深入的介紹。
1. 程序計(jì)數(shù)器
1.1. 什么是程序計(jì)數(shù)器?
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,可以把它看作當(dāng)前線程正在執(zhí)行的字節(jié)碼的行號(hào)指示器。也就是說(shuō),程序計(jì)數(shù)器里面記錄的是當(dāng)前線程正在執(zhí)行的那一條字節(jié)碼指令的地址。
注:但是,如果當(dāng)前線程正在執(zhí)行的是一個(gè)本地方法,那么此時(shí)程序計(jì)數(shù)器為空。
1.2. 程序計(jì)數(shù)器的作用
程序計(jì)數(shù)器有兩個(gè)作用:
- 字節(jié)碼解釋器通過(guò)改變程序計(jì)數(shù)器來(lái)依次讀取指令,從而實(shí)現(xiàn)代碼的流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。
- 在多線程的情況下,程序計(jì)數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來(lái)的時(shí)候能夠知道該線程上次運(yùn)行到哪兒了。
1.3. 程序計(jì)數(shù)器的特點(diǎn)
- 是一塊較小的存儲(chǔ)空間
- 線程私有。每條線程都有一個(gè)程序計(jì)數(shù)器。
- 是唯一一個(gè)不會(huì)出現(xiàn)OutOfMemoryError的內(nèi)存區(qū)域。
- 生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而死亡。
2. Java虛擬機(jī)棧(JVM Stack)
2.1. 什么是Java虛擬機(jī)棧?
Java虛擬機(jī)棧是描述Java方法運(yùn)行過(guò)程的內(nèi)存模型。
Java虛擬機(jī)棧會(huì)為每一個(gè)即將運(yùn)行的Java方法創(chuàng)建一塊叫做“棧幀”的區(qū)域,這塊區(qū)域用于存儲(chǔ)該方法在運(yùn)行過(guò)程中所需要的一些信息,這些信息包括:
- 局部變量表
存放基本數(shù)據(jù)類型變量、引用類型的變量、returnAddress類型的變量。
- 操作數(shù)棧
- 動(dòng)態(tài)鏈接
- 方法出口信息
- 等
當(dāng)一個(gè)方法即將被運(yùn)行時(shí),Java虛擬機(jī)棧首先會(huì)在Java虛擬機(jī)棧中為該方法創(chuàng)建一塊“棧幀”,棧幀中包含局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口信息等。當(dāng)方法在運(yùn)行過(guò)程中需要?jiǎng)?chuàng)建局部變量時(shí),就將局部變量的值存入棧幀的局部變量表中。
當(dāng)這個(gè)方法執(zhí)行完畢后,這個(gè)方法所對(duì)應(yīng)的棧幀將會(huì)出棧,并釋放內(nèi)存空間。
注意:人們常說(shuō),Java的內(nèi)存空間分為“棧”和“堆”,棧中存放局部變量,堆中存放對(duì)象。
這句話不完全正確!這里的“堆”可以這么理解,但這里的“棧”只代表了Java虛擬機(jī)棧中的局部變量表部分。真正的Java虛擬機(jī)棧是由一個(gè)個(gè)棧幀組成,而每個(gè)棧幀中都擁有:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口信息。
2.2. Java虛擬機(jī)棧的特點(diǎn)
- 局部變量表的創(chuàng)建是在方法被執(zhí)行的時(shí)候,隨著棧幀的創(chuàng)建而創(chuàng)建。而且,局部變量表的大小在編譯時(shí)期就確定下來(lái)了,在創(chuàng)建的時(shí)候只需分配事先規(guī)定好的大小即可。此外,在方法運(yùn)行的過(guò)程中局部變量表的大小是不會(huì)發(fā)生改變的。
- Java虛擬機(jī)棧會(huì)出現(xiàn)兩種異常:StackOverFlowError和OutOfMemoryError。
a) StackOverFlowError:
若Java虛擬機(jī)棧的內(nèi)存大小不允許動(dòng)態(tài)擴(kuò)展,那么當(dāng)線程請(qǐng)求棧的深度超過(guò)當(dāng)前Java虛擬機(jī)棧的最大深度的時(shí)候,就拋出StackOverFlowError異常。
b) OutOfMemoryError:
若Java虛擬機(jī)棧的內(nèi)存大小允許動(dòng)態(tài)擴(kuò)展,且當(dāng)線程請(qǐng)求棧時(shí)內(nèi)存用完了,無(wú)法再動(dòng)態(tài)擴(kuò)展了,此時(shí)拋出OutOfMemoryError異常。
- Java虛擬機(jī)棧也是線程私有的,每個(gè)線程都有各自的Java虛擬機(jī)棧,而且隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的死亡而死亡。
注:StackOverFlowError和OutOfMemoryError的異同?
StackOverFlowError表示當(dāng)前線程申請(qǐng)的棧超過(guò)了事先定好的棧的最大深度,但內(nèi)存空間可能還有很多。
而OutOfMemoryError是指當(dāng)線程申請(qǐng)棧時(shí)發(fā)現(xiàn)棧已經(jīng)滿了,而且內(nèi)存也全都用光了。
3. 本地方法棧
3.1. 什么是本地方法棧?
本地方法棧和Java虛擬機(jī)棧實(shí)現(xiàn)的功能類似,只不過(guò)本地方法區(qū)是本地方法運(yùn)行的內(nèi)存模型。
本地方法被執(zhí)行的時(shí)候,在本地方法棧也會(huì)創(chuàng)建一個(gè)棧幀,用于存放該本地方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、出口信息。
方法執(zhí)行完畢后相應(yīng)的棧幀也會(huì)出棧并釋放內(nèi)存空間。
也會(huì)拋出StackOverFlowError和OutOfMemoryError異常。
4. 堆
4.1. 什么是堆?
堆是用來(lái)存放對(duì)象的內(nèi)存空間。
幾乎所有的對(duì)象都存儲(chǔ)在堆中。
4.2. 堆的特點(diǎn)
- 線程共享
整個(gè)Java虛擬機(jī)只有一個(gè)堆,所有的線程都訪問(wèn)同一個(gè)堆。而程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧都是一個(gè)線程對(duì)應(yīng)一個(gè)的。
- 在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
- 垃圾回收的主要場(chǎng)所。
- 可以進(jìn)一步細(xì)分為:新生代、老年代。
新生代又可被分為:Eden、From Survior、To Survior。
不同的區(qū)域存放具有不同生命周期的對(duì)象。這樣可以根據(jù)不同的區(qū)域使用不同的垃圾回收算法,從而更具有針對(duì)性,從而更高效。
- 堆的大小既可以固定也可以擴(kuò)展,但主流的虛擬機(jī)堆的大小是可擴(kuò)展的,因此當(dāng)線程請(qǐng)求分配內(nèi)存,但堆已滿,且內(nèi)存已滿無(wú)法再擴(kuò)展時(shí),就拋出OutOfMemoryError。
5. 方法區(qū)
5.1. 什么是方法區(qū)?
Java虛擬機(jī)規(guī)范中定義方法區(qū)是堆的一個(gè)邏輯部分。
方法區(qū)中存放已經(jīng)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等。
5.2. 方法區(qū)的特點(diǎn)
- 線程共享
方法區(qū)是堆的一個(gè)邏輯部分,因此和堆一樣,都是線程共享的。整個(gè)虛擬機(jī)中只有一個(gè)方法區(qū)。
- 永久代
方法區(qū)中的信息一般需要長(zhǎng)期存在,而且它又是堆的邏輯分區(qū),因此用堆的劃分方法,我們把方法區(qū)稱為老年代。
- 內(nèi)存回收效率低
方法區(qū)中的信息一般需要長(zhǎng)期存在,回收一遍內(nèi)存之后可能只有少量信息無(wú)效。
對(duì)方法區(qū)的內(nèi)存回收的主要目標(biāo)是:對(duì)常量池的回收 和 對(duì)類型的卸載。
- Java虛擬機(jī)規(guī)范對(duì)方法區(qū)的要求比較寬松。
和堆一樣,允許固定大小,也允許可擴(kuò)展的大小,還允許不實(shí)現(xiàn)垃圾回收。
5.3. 什么是運(yùn)行時(shí)常量池?
方法區(qū)中存放三種數(shù)據(jù):類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼。其中常量存儲(chǔ)在運(yùn)行時(shí)常量池中。
我們一般在一個(gè)類中通過(guò)public static final來(lái)聲明一個(gè)常量。這個(gè)類被編譯后便生成Class文件,這個(gè)類的所有信息都存儲(chǔ)在這個(gè)class文件中。
當(dāng)這個(gè)類被Java虛擬機(jī)加載后,class文件中的常量就存放在方法區(qū)的運(yùn)行時(shí)常量池中。而且在運(yùn)行期間,可以向常量池中添加新的常量。如:String類的intern()方法就能在運(yùn)行期間向常量池中添加字符串常量。
當(dāng)運(yùn)行時(shí)常量池中的某些常量沒(méi)有被對(duì)象引用,同時(shí)也沒(méi)有被變量引用,那么就需要垃圾收集器回收。
6. 直接內(nèi)存
直接內(nèi)存是除Java虛擬機(jī)之外的內(nèi)存,但也有可能被Java使用。
在NIO中引入了一種基于通道和緩沖的IO方式。它可以通過(guò)調(diào)用本地方法直接分配Java虛擬機(jī)之外的內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象直接操作該內(nèi)存,而無(wú)需先將外面內(nèi)存中的數(shù)據(jù)復(fù)制到堆中再操作,從而提升了數(shù)據(jù)操作的效率。
直接內(nèi)存的大小不受Java虛擬機(jī)控制,但既然是內(nèi)存,當(dāng)內(nèi)存不足時(shí)就會(huì)拋出OOM異常。
綜上所述
- Java虛擬機(jī)的內(nèi)存模型中一共有兩個(gè)“棧”,分別是:Java虛擬機(jī)棧和本地方法棧。
兩個(gè)“棧”的功能類似,都是方法運(yùn)行過(guò)程的內(nèi)存模型。并且兩個(gè)“棧”內(nèi)部構(gòu)造相同,都是線程私有。
只不過(guò)Java虛擬機(jī)棧描述的是Java方法運(yùn)行過(guò)程的內(nèi)存模型,而本地方法棧是描述Java本地方法運(yùn)行過(guò)程的內(nèi)存模型。
- Java虛擬機(jī)的內(nèi)存模型中一共有兩個(gè)“堆”,一個(gè)是原本的堆,一個(gè)是方法區(qū)。方法區(qū)本質(zhì)上是屬于堆的一個(gè)邏輯部分。堆中存放對(duì)象,方法區(qū)中存放類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯的代碼。
- 堆是Java虛擬機(jī)中最大的一塊內(nèi)存區(qū)域,也是垃圾收集器主要的工作區(qū)域。
- 程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧是線程私有的,即每個(gè)線程都擁有各自的程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法區(qū)。并且他們的生命周期和所屬的線程一樣。
而堆、方法區(qū)是線程共享的,在Java虛擬機(jī)中只有一個(gè)堆、一個(gè)方法棧。并在JVM啟動(dòng)的時(shí)候就創(chuàng)建,JVM停止才銷(xiāo)毀。
|