JVM 內(nèi)存模型

JVM內(nèi)存模型


JVM內(nèi)存模型

JVM將內(nèi)存劃分為6個部分:

PC寄存器(也叫程序計數(shù)器):記錄當(dāng)前線程運(yùn)行位置,每個線程都有一個獨(dú)立的程序計數(shù)器,線程的阻塞、回復(fù)、掛起等操作都需要程序計數(shù)器的參與,因此是線程私有的。

虛擬機(jī)棧:創(chuàng)建線程時創(chuàng)建,用來存儲棧幀,因此也是線程私有。java程序中的方法執(zhí)行時,會創(chuàng)建一個棧幀,用于存儲臨時數(shù)據(jù) 、中間結(jié)果、局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。溢出會報StackOverflowError。

本地方法棧:支持native方法,如在java中調(diào)用C/C++。

:所有線程共享,用于存儲對象。堆空間不夠,同時無法申請足夠內(nèi)存時,會報OutOfMemoryError。

方法區(qū):各個線程共享,存儲靜態(tài)變量、運(yùn)行時常量池等信息。

運(yùn)行時常量池:每個類一個,從class常量池中遷移過來,程序中使用的常量值。

各部分作用


PC寄存器

最小的一塊內(nèi)存區(qū)域,它的作用是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器,在虛擬機(jī)的模型里,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴計數(shù)器完成。


虛擬機(jī)棧

描述的是java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會創(chuàng)建一個"棧幀",用于存儲局部變量表(包括參數(shù))、操作棧、方法出口等信息。每個方法被調(diào)用到執(zhí)行完的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。聲明周期與線程相同,是線程私有的。

棧幀由三部分組成:局部變量區(qū)、操作數(shù)棧、幀數(shù)據(jù)區(qū)。局部變量區(qū)被組織為以一個字長為單位、從0開始計數(shù)的數(shù)組,和局部變量區(qū)一樣,操作數(shù)棧也被組織成一個以字長為單位的數(shù)組。但和前者不同的是,它不是通過索引來訪問的,而是通過入棧和出棧來訪問的,可以看作為臨時數(shù)據(jù)的存儲區(qū)域。除了局部變量區(qū)和操作數(shù)棧外,java棧幀還需要一些數(shù)據(jù)來支持常量池解析、正常方法返回以及異常派發(fā)機(jī)制。這些數(shù)據(jù)都保存在java棧幀的幀數(shù)據(jù)區(qū)中。

局部變量表:?存放了編譯器可知的各種基本數(shù)據(jù)類型、對象引用(引用指針,并非對象本身),其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量的空間,其余數(shù)據(jù)類型只占1個。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個方法時,這個方法需要在棧幀中分配多大的局部變量是完全確定的,在運(yùn)行期間棧幀不會改變局部變量表的大小空間。


本地方法棧

與虛擬機(jī)?;绢愃?,區(qū)別在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行的java方法服務(wù),而本地方法棧則是為Native方法服務(wù)。(棧的空間大小遠(yuǎn)遠(yuǎn)小于堆)


是java虛擬機(jī)所管理的內(nèi)存中最大的一塊內(nèi)存區(qū)域,也是被各個線程共享的內(nèi)存區(qū)域,該內(nèi)存區(qū)域存放了對象實(shí)例及數(shù)組(但不是所有的對象實(shí)例都在堆中)。

其大小通過-Xms(最小值)和-Xmx(最大值)參數(shù)設(shè)置(最大最小值都要小于1G),前者為啟動時申請的最小內(nèi)存,默認(rèn)為操作系統(tǒng)物理內(nèi)存的1/64,后者為JVM可申請的最大內(nèi)存,默認(rèn)為物理內(nèi)存的1/4,默認(rèn)當(dāng)空余堆內(nèi)存小于40%時,JVM會增大堆內(nèi)存到-Xmx指定的大小,可通過-XX:MinHeapFreeRation=來指定這個比列;當(dāng)空余堆內(nèi)存大于70%時,JVM會減小堆內(nèi)存的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=來指定這個比列,當(dāng)然為了避免在運(yùn)行時頻繁調(diào)整Heap的大小,通常-Xms與-Xmx的值設(shè)成一樣

堆內(nèi)存 = 新生代+老生代+持久代。在我們垃圾回收的時候,我們往往將堆內(nèi)存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1組成,三者的比例是8:1:1,新生代的回收機(jī)制采用復(fù)制算法,在Minor GC的時候,我們都留一個存活區(qū)用來存放存活的對象,真正進(jìn)行的區(qū)域是Eden+其中一個存活區(qū),當(dāng)我們的對象時長超過一定年齡時(默認(rèn)15歲,可以通過參數(shù)設(shè)置),將會把對象放入老生代,當(dāng)然大的對象會直接進(jìn)入老生代。老生代采用的回收算法是標(biāo)記整理算法


方法區(qū)

方法區(qū)也稱"永久代",它用于存儲虛擬機(jī)加載的類信息、常量、靜態(tài)變量、是各個線程共享的內(nèi)存區(qū)域。默認(rèn)最小值為16MB,最大值為64MB(64位JVM由于指針膨脹,默認(rèn)是85M),可以通過-XX:PermSize 和 -XX:MaxPermSize 參數(shù)限制方法區(qū)的大小。它是一片連續(xù)的堆空間,永久代的垃圾收集是和老年代捆綁在一起的,因此無論誰滿了,都會觸發(fā)永久代和老年代的垃圾收集。

JDK7開始移除永久代(但并沒有移除,還是存在),貯存在永久代的一部分?jǐn)?shù)據(jù)已經(jīng)轉(zhuǎn)移到了Java Heap或者是Native Heap:符號引用(Symbols)轉(zhuǎn)移到了native heap;字面量(interned strings)轉(zhuǎn)移到了java heap;類的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap。

JDK8開始使用元空間(Metaspace),將類元數(shù)據(jù)放到本地內(nèi)存中,另外,將常量池和靜態(tài)變量放到Java堆里。HotSpot VM將會為類的元數(shù)據(jù)明確分配和釋放本地內(nèi)存。在這種架構(gòu)下,類元信息就突破了原來-XX:MaxPermSize的限制,現(xiàn)在可以使用更多的本地內(nèi)存。這樣就從一定程度上解決了原來在運(yùn)行時生成大量類造成經(jīng)常Full GC問題,如運(yùn)行時使用反射、代理等。所以升級以后Java堆空間可能會增加。

元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過元空間與永久代之間的最大區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制,但可以通過參數(shù)指定元空間的大小。


運(yùn)行時常量池

運(yùn)行時常量池(Runtime Constant Pool):是方法區(qū)的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯器生成的各種符號引用,這部分內(nèi)容將在類加載后放到方法區(qū)的運(yùn)行時常量池中。


堆外內(nèi)存

堆外內(nèi)存(直接內(nèi)存)并不是虛擬機(jī)內(nèi)存的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,內(nèi)存對象分配在Java虛擬機(jī)的堆以外的內(nèi)存,這些內(nèi)存直接受操作系統(tǒng)管理(而不是虛擬機(jī)),這樣做的結(jié)果就是能夠在一定程度上減少垃圾回收對應(yīng)用程序造成的影響。使用未公開的Unsafe和NIO包下ByteBuffer來創(chuàng)建堆外內(nèi)存。

為什么使用堆外內(nèi)存

1、減少了垃圾回收:使用堆外內(nèi)存的話,堆外內(nèi)存是直接受操作系統(tǒng)管理( 而不是虛擬機(jī) )。這樣做的結(jié)果就是能保持一個較小的堆內(nèi)內(nèi)存,以減少垃圾收集對應(yīng)用的影響。

2、提升復(fù)制速度(IO效率):堆內(nèi)內(nèi)存由JVM管理,屬于“用戶態(tài)”;而堆外內(nèi)存由OS管理,屬于“內(nèi)核態(tài)”。如果從堆內(nèi)向磁盤寫數(shù)據(jù)時,數(shù)據(jù)會被先復(fù)制到堆外內(nèi)存,即內(nèi)核緩沖區(qū),然后再由OS寫入磁盤,使用堆外內(nèi)存避免了這個操作。

堆外內(nèi)存申請

JDK的ByteBuffer類提供了一個接口allocateDirect(int capacity)進(jìn)行堆外內(nèi)存的申請,底層通過unsafe.allocateMemory(size)實(shí)現(xiàn)。Netty、Mina等框架提供的接口也是基于ByteBuffer封裝的。?

堆外內(nèi)存釋放

1、主動回收:?對于Sun的JDK,只要從DirectByteBuffer里取出里面的Cleaner對象,然后調(diào)用它的clean()就行;?

2、基于 GC 回收:堆內(nèi)的DirectByteBuffer對象被GC時,會調(diào)用cleaner回收其引用的堆外內(nèi)存。問題是YGC只會將將新生代里的不可達(dá)的DirectByteBuffer對象及其堆外內(nèi)存回收,如果有大量的DirectByteBuffer對象移到了old區(qū),但是又一直沒有做CMS GC或者FGC,而只進(jìn)行YGC,物理內(nèi)存會被慢慢耗光,觸發(fā)OOM;

堆外內(nèi)存溢出

Java.nio.DirectByteBuffer所需的內(nèi)存超過了物理分配的堆外內(nèi)存,出現(xiàn)”java.lang.OutOfMemoryError: Direct buffer memory”。

堆外內(nèi)存大小

默認(rèn)大?。?-Xmx值) - (1個survivor大小)

設(shè)置大?。?XX:MaxDirectMemorySize來指定最大的堆外內(nèi)存大小

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

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

  • 文章轉(zhuǎn)自 http://blog.csdn.net/u012152619/article/details/4696...
    云狗狗狗狗狗閱讀 675評論 1 4
  • Java虛擬機(jī)在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自不同的用途,...
    join_a922閱讀 430評論 0 0
  • 我們知道,計算機(jī)CPU和內(nèi)存的交互是最頻繁的,內(nèi)存是我們的高速緩存區(qū),用戶磁盤和CPU的交互,而CPU運(yùn)轉(zhuǎn)速度越來...
    join_a922閱讀 311評論 0 0
  • 昨天我沒上班我陪孩子準(zhǔn)備的東西,今天早上我急急忙忙去上班都把給孩子準(zhǔn)備的東西都拉家里了!
    海低星閱讀 114評論 0 0
  • 他帶著妻兒向我走來 身上散發(fā)著泥土味 微笑著對我說 他很陶醉 他慢慢向我走來 身上透著煙草味 唉聲嘆氣的對我說 他...
    竹園居士閱讀 207評論 0 1

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