運(yùn)行時(shí)數(shù)據(jù)區(qū)域
Java虛擬機(jī)在執(zhí)行java程序的過程中會(huì)將它所管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域队伟,這些區(qū)域有各自用途闽瓢,以及創(chuàng)建和銷毀時(shí)間胁住,有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在层宫,有些區(qū)域依賴用戶線程的啟動(dòng)和結(jié)束而建立和銷毀学密。
Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)如下圖所示:
從圖中揭蜒,我們看到了5大區(qū)域:線程共享的方法區(qū)和堆横浑,線程私有的java虛擬機(jī)棧,本地方法棧以及程序計(jì)數(shù)器屉更。
程序計(jì)數(shù)器:(Program Counter Register)這個(gè)區(qū)域是唯一一個(gè)不會(huì)拋出OutOfMemoryError異常的區(qū)域徙融。它是一塊比較小的內(nèi)存,是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器瑰谜。為了線程切換后能恢復(fù)到正確的執(zhí)行位置欺冀,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)算器,各條線程之間計(jì)算器互不影響萨脑。
Java虛擬機(jī)棧:也是線程私有的隐轩,它的生命周期與線程相同,它描述的是java方法執(zhí)行的內(nèi)存模型渤早,每個(gè)方法在執(zhí)行的時(shí)候會(huì)創(chuàng)建一個(gè)棧幀用來存儲(chǔ)局部變量职车,操作數(shù),動(dòng)態(tài)鏈接等。每個(gè)方法從調(diào)用直至執(zhí)行完成的過程悴灵,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧和出棧的過程军援。
本地方法棧:和java虛擬機(jī)棧功能類似,只不過java虛擬機(jī)棧執(zhí)行的是java字節(jié)碼而本地方法棧執(zhí)行的是Native方法称勋。
Java堆:Java堆是虛擬機(jī)所管理的內(nèi)存中最大的一塊,Java堆是被所有線程共享的一塊內(nèi)存區(qū)域涯竟,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存赡鲜。java堆可以細(xì)分出新生代和老年代,再細(xì)致一點(diǎn)可以分為:Eden庐船,F(xiàn)rom Survivor银酬,To Survivor等空間。
方法區(qū):該區(qū)域用來存儲(chǔ)已經(jīng)被虛擬機(jī)加載過來的類信息筐钟,常量揩瞪,靜態(tài)變量等。方法區(qū)導(dǎo)致內(nèi)存問題實(shí)例請(qǐng)參考:Android性能優(yōu)化-方法區(qū)導(dǎo)致內(nèi)存問題實(shí)例分析篓冲。
以上講完了JVM運(yùn)行時(shí)內(nèi)存區(qū)域的5大塊李破,同時(shí)需要補(bǔ)充的一點(diǎn)是還有一個(gè)運(yùn)行時(shí)常量池,它也是方法區(qū)的一部分壹将。Class文件中除了有類的版本嗤攻,字段,接口诽俯,方法等描述信息外妇菱,還有一項(xiàng)信息就是常量池,用來存放編譯時(shí)期生成的各種字面量和符號(hào)引用暴区。但是需要注意的是:Java語言并不要求常量一定是在編譯期間產(chǎn)生闯团,也就是并非與裝入class文件中的常量池的內(nèi)容才能進(jìn)入到方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入常量池中仙粱,如String.intern()方法房交。
運(yùn)行時(shí)不同數(shù)據(jù)區(qū)域異常
(1)除程序計(jì)算器外,虛擬機(jī)內(nèi)存的其他幾個(gè)運(yùn)行時(shí)區(qū)都會(huì)發(fā)生OutOfMemoryError伐割。
(2)使Java堆發(fā)生內(nèi)存溢出的思路:只要不斷創(chuàng)建對(duì)象涌萤,并保證GC Roots到對(duì)象之間有可達(dá)路徑來避免垃圾回收機(jī)制清除這些對(duì)象即可。
(3)使方法區(qū)發(fā)生類導(dǎo)致的內(nèi)存溢出基本思路:在運(yùn)行時(shí)產(chǎn)生大量的類去填滿方法區(qū)口猜,也就是在運(yùn)行時(shí)動(dòng)態(tài)產(chǎn)生很多的類负溪,直到方法區(qū)內(nèi)存溢出。所以頻繁動(dòng)態(tài)產(chǎn)生很多類時(shí)济炎,需要注意方法區(qū)內(nèi)存溢出川抡,具體內(nèi)容請(qǐng)參考Android性能優(yōu)化-方法區(qū)導(dǎo)致內(nèi)存問題實(shí)例分析。
(4)虛擬機(jī)棧和本地方法棧兩種異常:
a) 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度(即方法調(diào)用深度超過最大允許深度),將拋出StackOverFlowError異常.
b) 如果虛擬機(jī)在擴(kuò)展棧時(shí)無法申請(qǐng)到足夠的內(nèi)存崖堤,則拋出utOfMemoryError異常侍咱。
HotSpot虛擬機(jī)對(duì)象創(chuàng)建、對(duì)象內(nèi)存布局密幔、對(duì)象訪問定位
對(duì)象創(chuàng)建
(1)對(duì)象創(chuàng)建的幾種方法:new楔脯、克隆、反序列化胯甩;
(2)對(duì)象創(chuàng)建過程:
步驟一:虛擬機(jī)在遇到new指令時(shí)昧廷,首先會(huì)去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用并且檢查該符號(hào)引用代表的類是否已經(jīng)被加載,解析和初始化過偎箫。如果沒有木柬,那就必須先執(zhí)行相應(yīng)的類加載。
步驟二:在類經(jīng)過加載檢查后淹办,虛擬機(jī)就需要為新生對(duì)象分配內(nèi)存了眉枕。對(duì)象所需要的內(nèi)存大小在類加載完成之后就可以確定。對(duì)象分配內(nèi)存空間的任務(wù)等同于把一塊確定大小的內(nèi)存從java堆中劃分出來怜森,如果內(nèi)存絕對(duì)規(guī)整速挑,采用指針碰撞分配方式(即移動(dòng)指針到與對(duì)象大小相等的距離),如果內(nèi)存不規(guī)整采用空間列表的分配方式副硅。
步驟三:在分配完內(nèi)存之后梗摇,虛擬機(jī)需要將分配到的內(nèi)存空間初始化為零值。
步驟四:接下來想许,虛擬機(jī)會(huì)對(duì)對(duì)象進(jìn)行必要的設(shè)置伶授,如對(duì)象的hash碼,對(duì)象的GC分代信息等流纹。
步驟五:最后執(zhí)行對(duì)象的init方法把對(duì)象按照程序員意愿進(jìn)行初始化糜烹,這樣一個(gè)真正的對(duì)象才算完全產(chǎn)生出來。
對(duì)象內(nèi)存布局
對(duì)象內(nèi)存布局分為三塊區(qū)域:對(duì)象頭漱凝、實(shí)例數(shù)據(jù)疮蹦、對(duì)齊填充。
對(duì)象頭:主要存儲(chǔ)了2部分信息茸炒,第一部分是對(duì)象自身運(yùn)行的數(shù)據(jù)愕乎,如hashcode,GC分代等信息壁公;
第二部分是類型指針感论,就是對(duì)象對(duì)它的類元數(shù)據(jù)指針,其實(shí)就是一個(gè)引用紊册。虛擬機(jī)通過這個(gè)指針(引用)來確定對(duì)象是哪個(gè)類的實(shí)例比肄。
實(shí)例數(shù)據(jù)(Instance Data):對(duì)象真正存儲(chǔ)的有效信息,也就是程序代碼中所寫的各種類型的字段內(nèi)容。
對(duì)齊填充(Padding):這個(gè)不是必然存在的芳绩。HotSpot虛擬機(jī)自動(dòng)內(nèi)存管理要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍掀亥,也就是對(duì)象大小必須是8字節(jié)的整數(shù)倍,對(duì)象實(shí)例數(shù)據(jù)部分沒有對(duì)齊時(shí)妥色,需要通過對(duì)齊填充來補(bǔ)齊搪花。
對(duì)象的訪問定位
我們知道了對(duì)象的創(chuàng)建,內(nèi)存布局等相關(guān)內(nèi)容之后嘹害,需要知道存儲(chǔ)的對(duì)象如何找到呢撮竿?這就涉及到對(duì)象的定位問題了。我們java程序需要通過棧上的引用數(shù)據(jù)來操作具體的對(duì)象吼拥。對(duì)對(duì)象的訪問方式取決于虛擬機(jī)的實(shí)現(xiàn),目前比較主流的有句柄和直接指針兩種方式线衫。下面讓我們看看這兩種方式吧凿可,直接上圖:
第1張圖是通過句柄的方式對(duì)對(duì)象進(jìn)行訪問,在java堆中劃分出來一塊內(nèi)存作為句柄池授账,而reference中存儲(chǔ)的是對(duì)象的句柄地址枯跑,句柄中存儲(chǔ)了對(duì)象實(shí)例等信息。第2張圖是通過直接指針的方式白热,reference中存儲(chǔ)的是實(shí)例對(duì)象的地址敛助。
這兩種對(duì)象引用的方式各有千秋,通過句柄的好處是reference中存儲(chǔ)的是穩(wěn)定的句柄地址屋确,在對(duì)象被移動(dòng)的時(shí)候只會(huì)改變句柄的實(shí)例指針而reference本身不需要修改纳击;使用直接指針的好處是速度開,不需要在java堆中在劃分出一塊內(nèi)存區(qū)域同時(shí)節(jié)省了指針定位的開銷攻臀。但是就HotSpot而言焕数,采用的是直接指針方式。
通過以上內(nèi)容刨啸,我們明白了虛擬機(jī)中的內(nèi)存是如何劃分的堡赔,那部分區(qū)域,什么樣的代碼和操作可能導(dǎo)致內(nèi)存異常溢出異常设联。雖然java有垃圾收集機(jī)制善已,但是內(nèi)存溢出離我們并不遙遠(yuǎn),下一篇文章將講解Java垃圾收集機(jī)為避免內(nèi)存溢出異常的出現(xiàn)做了哪些努力离例。
以上就是Java內(nèi)存區(qū)域與內(nèi)存溢出異常相關(guān)內(nèi)容换团,大部分內(nèi)容直接從小臘月虛擬機(jī)相關(guān)文章直接拷貝而來(節(jié)省打字時(shí)間)。
JVM學(xué)習(xí)資料
《深入理解Java虛擬機(jī)》
Java虛擬機(jī)原理圖解系列文章
小臘月虛擬機(jī)相關(guān)文章