JVM內(nèi)存結(jié)構(gòu)
注意: 由于微信的限制,本文中的超鏈接可能不會(huì)在文中顯示,建議點(diǎn)擊文末的 閱讀原文 查閱。
JVM內(nèi)存結(jié)構(gòu)
Java虛擬機(jī)的內(nèi)存空間分為5個(gè)部分:
?程序計(jì)數(shù)器?Java虛擬機(jī)棧?本地方法棧?堆?方法區(qū)

JDK 1.8同JDK 1.7比,最大的差別是: 元數(shù)據(jù)區(qū)取代了永久代。元空間的本質(zhì)和永久代類似,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元數(shù)據(jù)空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。
程序計(jì)數(shù)器(PC寄存器)
程序計(jì)數(shù)器的定義
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,是當(dāng)前線程正在執(zhí)行的那條字節(jié)碼指令的地址。若當(dāng)前線程正在執(zhí)行的是一個(gè)本地方法,那么此時(shí)程序計(jì)數(shù)器為 Undefined。
程序計(jì)數(shù)器的作用
?字節(jié)碼解釋器通過改變程序計(jì)數(shù)器來依次讀取指令,從而實(shí)現(xiàn)代碼的流程控制。?在多線程情況下,程序計(jì)數(shù)器記錄的是當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程切換回來時(shí),就知道上次線程執(zhí)行到哪了。
程序計(jì)數(shù)器的特點(diǎn)
?是一塊較小的內(nèi)存空間。?線程私有,每條線程都有自己的程序計(jì)數(shù)器。?生命周期:隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而銷毀。?是唯一一個(gè)不會(huì)出現(xiàn) OutOfMemoryError 的內(nèi)存區(qū)域
Java虛擬機(jī)棧(Java棧)
Java虛擬機(jī)棧的定義
Java虛擬機(jī)棧是描述Java方法運(yùn)行過程的內(nèi)存模型。
Java虛擬機(jī)棧會(huì)為每一個(gè)即將運(yùn)行的Java方法創(chuàng)建一塊叫做 “棧幀” 的區(qū)域,用于存放該方法運(yùn)行過程中的一些信息,如:
?局部變量表?操作數(shù)棧?動(dòng)態(tài)鏈接?方法出口信息?……

壓棧出棧過程
當(dāng)方法運(yùn)行過程中需要?jiǎng)?chuàng)建局部變量時(shí),就將局部變量的值存入棧幀中的局部變量表中。
Java虛擬機(jī)棧的棧頂是棧幀當(dāng)前正在執(zhí)行的活動(dòng)棧,也就是當(dāng)前正在執(zhí)行的方法,PC寄存器也會(huì)指向這個(gè)地址。只有這個(gè)活動(dòng)的棧幀的本地變量可以被操作數(shù)棧使用,當(dāng)在這個(gè)棧幀中調(diào)用另一個(gè)方法,與之對(duì)應(yīng)的棧幀又會(huì)被創(chuàng)建,新創(chuàng)建的棧幀壓入棧頂,變?yōu)楫?dāng)前的活動(dòng)棧幀。
方法結(jié)束后,當(dāng)前棧幀被移出,棧幀的返回值變成新的活動(dòng)棧幀中操作數(shù)棧的一個(gè)操作數(shù)。如果沒有返回值,那么新的活動(dòng)棧幀中操作數(shù)棧的操作數(shù)沒有變化。
由于Java虛擬機(jī)棧是與線程對(duì)應(yīng)的,數(shù)據(jù)不是線程共享的,因此不用關(guān)心數(shù)據(jù)一致性問題,也不會(huì)存在同步鎖的問題。
Java虛擬機(jī)棧的特點(diǎn)
?局部變量表隨著棧幀的創(chuàng)建而創(chuàng)建,它的大小在編譯時(shí)確定,創(chuàng)建時(shí)只需分配事先規(guī)定的大小即可。在方法運(yùn)行過程中,局部變量表的大小不會(huì)發(fā)生改變。?Java虛擬機(jī)棧會(huì)出現(xiàn)兩種異常: StackOverFlowError 和 OutOfMemoryError。?StackOverflowError —— 若Java虛擬機(jī)棧的大小不允許動(dòng)態(tài)擴(kuò)展,那么當(dāng)線程請(qǐng)求棧的深度超過當(dāng)前Java虛擬機(jī)棧的最大深度時(shí),拋出 StackoverflowError 異常。?OutOfMemoryError —— 若允許動(dòng)態(tài)擴(kuò)展,那么當(dāng)線程請(qǐng)求棧時(shí)內(nèi)存用完了,無(wú)法再動(dòng)態(tài)擴(kuò)展時(shí),拋出 OutOfMemoryError 異常。?Java虛擬機(jī)棧也是線程私有,隨著線程創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而銷毀。
出現(xiàn) StackOverflowError 時(shí),內(nèi)存空間可能還有很多。
本地方法棧(C棧)
本地方法棧的定義
本地方法棧是為JVM運(yùn)行Native方法準(zhǔn)備的空間,由于很多Native方法都是用C語(yǔ)言實(shí)現(xiàn)的,所以它通常又叫C棧。它與Java虛擬機(jī)棧實(shí)現(xiàn)的功能類似,只不過本地方法棧是描述本地方法運(yùn)行過程的內(nèi)存模型。
棧幀變化過程
本地方法被執(zhí)行時(shí),在本地方法棧也會(huì)創(chuàng)建一塊棧幀,用于存放該方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口信息等。
方法執(zhí)行結(jié)束后,相應(yīng)的棧幀也會(huì)出棧,并釋放內(nèi)存空間。也會(huì)拋出 StackoverflowError 和 OutOfMemoryError 異常。
如果Java虛擬機(jī)本身不支持Native方法,或是本身不依賴于傳統(tǒng)棧,那么可以不提供本地方法棧。如果支持本地方法棧,那么這個(gè)棧一般會(huì)在線程創(chuàng)建的時(shí)候按線程分配。
堆
堆的定義
堆是用來存放對(duì)象的內(nèi)存空間,幾乎所有的對(duì)象都存儲(chǔ)在堆中。
堆的特點(diǎn)
?線程共享,整個(gè)Java虛擬機(jī)只有一個(gè)堆,所有的線程都訪問同一個(gè)堆。而程序計(jì)數(shù)器、Java虛擬機(jī)棧、本地方法棧都是一個(gè)線程對(duì)應(yīng)一個(gè)。?在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。?是垃圾回收的主要場(chǎng)所。?進(jìn)一步可分為:新生代(Eden區(qū): From Survivor, To Survivor )、老年代。
不同的區(qū)域存放不同生命周期的對(duì)象,這樣可以根據(jù)不同的區(qū)域使用不同的垃圾回收算法,更具有針對(duì)性。
堆的大小既可以固定也可以擴(kuò)展,但對(duì)于主流的虛擬機(jī),堆的大小是可擴(kuò)展的,因此當(dāng)線程請(qǐng)求分配內(nèi)存,但堆已滿,且內(nèi)存已無(wú)法再擴(kuò)展時(shí),就拋出 OutOfMemoryError 異常。
Java堆所使用的內(nèi)存不需要保證是連續(xù)的。而由于堆是被所有線程共享的,所以對(duì)它的訪問需要注意同步問題,方法和對(duì)應(yīng)的屬性都需要保證一致性。
方法區(qū)
方法區(qū)的定義
Java虛擬機(jī)規(guī)范中定義方法區(qū)是堆的一個(gè)邏輯部分。方法區(qū)存放以下信息:
?已經(jīng)被虛擬機(jī)加載的類信息?常量?靜態(tài)變量?即時(shí)編譯器編譯后的代碼
方法區(qū)的特點(diǎn)
?線程共享。方法區(qū)是堆的一個(gè)邏輯部分,因此和堆一樣,都是線程共享的。整個(gè)虛擬機(jī)中只有一個(gè)方法區(qū)。?永久代。方法區(qū)中的信息一般需要長(zhǎng)期存在,而且它又是堆的邏輯分區(qū),因此用堆的劃分方法,把方法區(qū)成為“永久代”。?內(nèi)存回收效率低。方法區(qū)中的信息一般需要長(zhǎng)期存在,回收一遍之后可能只有少量信息無(wú)效。主要回收目標(biāo)是:對(duì)常量池的回收;對(duì)類型的卸載。?Java虛擬機(jī)規(guī)范對(duì)方法區(qū)的要求比較寬松。和堆一樣,允許固定大小,也允許動(dòng)態(tài)擴(kuò)展,還允許不實(shí)現(xiàn)垃圾回收。
運(yùn)行時(shí)常量池
方法區(qū)中存放:類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼。常量就存放再運(yùn)行時(shí)常量池中。
當(dāng)類被Java虛擬機(jī)加載后, .class 文件中的常量就存放在方法區(qū)的運(yùn)行時(shí)常量池中。而且在運(yùn)行期間,可以向常量池中添加新的常量。如 String 類的 intern() 方法就能在運(yùn)行期間向常量池中添加字符串常量。
直接內(nèi)存(對(duì)外內(nèi)存)
直接內(nèi)存是除Java虛擬機(jī)之外的內(nèi)存,但也可能被Java使用。
操作直接內(nèi)存
在NIO中引入了一種基于通道和緩沖的IO方式。它可以通過調(diào)用本地方法直接分配Java虛擬機(jī)之外的內(nèi)存,然后通過一個(gè)存儲(chǔ)在堆中的 DirectByteBuffer 對(duì)象直接操作該內(nèi)存,而無(wú)須先將外部?jī)?nèi)存中的數(shù)據(jù)復(fù)制到堆中再進(jìn)行操作,從而提高了數(shù)據(jù)操作的效率。
直接內(nèi)存的大小不受Java虛擬機(jī)控制,但既然是內(nèi)存,當(dāng)內(nèi)存不足時(shí)就會(huì)拋出 OutOfMemoryError 異常。
直接內(nèi)粗與堆內(nèi)內(nèi)存比較
?直接內(nèi)存申請(qǐng)空間耗費(fèi)更高的性能?直接內(nèi)存讀取IO的性能要優(yōu)于普通的堆內(nèi)存?直接內(nèi)存作用鏈:本地IO → 直接內(nèi)存 → 本地IO?堆內(nèi)存作用鏈:本地IO → 直接內(nèi)存 → 非直接內(nèi)存 → 直接內(nèi)存 → 本地IO
服務(wù)器管理員再配置虛擬機(jī)參數(shù)時(shí),會(huì)根據(jù)實(shí)際內(nèi)存設(shè)置
-Xmx等參數(shù)信息,但經(jīng)常忽略直接內(nèi)存,使得各個(gè)內(nèi)存區(qū)域總和大于物理內(nèi)存限制,從而導(dǎo)致動(dòng)態(tài)擴(kuò)展時(shí)出現(xiàn)OutOfMemoryError異常。
Article From:JVM 內(nèi)存結(jié)構(gòu)
注意: 由于微信的限制,本文中的超鏈接可能不會(huì)在文中顯示,建議點(diǎn)擊文末的 閱讀原文 查閱。
歡迎關(guān)注我的公眾號(hào)“須彌零一”,更多技術(shù)文章第一時(shí)間推送。
