上面的圖片是Java虛擬機運行時數(shù)據(jù)區(qū)。
1.程序計數(shù)器:
是一塊較小的內(nèi)存空間州弟,可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器速挑,改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。
關(guān)鍵字解讀:
1)“當前線程”代表著每個線程都有自己的程序計數(shù)器锋爪,互不影響丙曙,獨立存儲爸业。所以這類內(nèi)存區(qū)域為線程私有的內(nèi)存。
2)字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令亏镰,分支扯旷、循環(huán)、跳轉(zhuǎn)索抓、異常處理钧忽、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器完成。
線程在執(zhí)行Java方法逼肯,計數(shù)器記錄正在執(zhí)行的虛擬機字節(jié)碼指令的地址
線程在執(zhí)行Native方法耸黑,計數(shù)器值為空
此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定OutOfMemoryError的區(qū)域
2.虛擬機棧:
1)也是線程私有,生命周期與線程相同篮幢。
2)虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀用于存儲局部變量表大刊、操作數(shù)棧、動態(tài)鏈接洲拇、方法出口等信息奈揍;每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程赋续。
3)若單個線程請求的棧深度大于虛擬機允許的深度男翰,則會拋出StackOverflowError(棧溢出錯誤)。JVM會為每個線程的虛擬機棧分配一定的內(nèi)存大信β摇(-Xss參數(shù))蛾绎,因此虛擬機棧能夠容納的棧幀數(shù)量是有限的,若棧幀不斷進棧而不出棧鸦列,最終會導(dǎo)致當前線程虛擬機棧的內(nèi)存空間耗盡租冠,拋出異常。
4)不同于StackOverflowError薯嗤,OutOfMemoryError指的是當整個虛擬機棧內(nèi)存耗盡顽爹,并且無法再申請到新的內(nèi)存時拋出的異常。
JVM未提供設(shè)置整個虛擬機棧占用內(nèi)存的配置參數(shù)骆姐。虛擬機棧的最大內(nèi)存大致上等于“JVM進程能占用的最大內(nèi)存(依賴于具體操作系統(tǒng)) - 最大堆內(nèi)存 - 最大方法區(qū)內(nèi)存 - 程序計數(shù)器內(nèi)存(可以忽略不計) - JVM進程本身消耗內(nèi)存”镜粤。當虛擬機棧能夠使用的最大內(nèi)存被耗盡后,便會拋出OutOfMemoryError玻褪,
3.本地方法棧:
與虛擬機棧發(fā)揮的作用非常相似肉渴。為虛擬機使用的Native方法服務(wù)。本地方法棧的功能和特點類似于虛擬機棧带射,均具有線程隔離的特點以及都能拋出StackOverflowError和OutOfMemoryError異常同规。不同的是,本地方法棧服務(wù)的對象是JVM執(zhí)行的native方法,而虛擬機棧服務(wù)的是JVM執(zhí)行的java方法券勺。
有的虛擬機把虛擬機棧和本地方法棧合二為一绪钥。也有虛擬機棧的異常。
4.堆:
對大多數(shù)應(yīng)用程序來說朱灿,堆是Java虛擬機管理的內(nèi)存中最大的一塊昧识。
Java堆是被所有線程共享的一塊數(shù)據(jù)區(qū)域,在虛擬機啟動時創(chuàng)建盗扒,這一內(nèi)存區(qū)域的唯一目的就是存放對象實例跪楞,幾乎所有的對象實例都在這里分配內(nèi)存。
但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)逐漸成熟侣灶,棧上分配甸祭、標量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生,所有的對象都分配在堆上也逐漸變得不是那么“絕對”褥影。
堆中可細分為新生代和老年代池户,在細分可以分為Eden空間、Form Survivor空間凡怎、to Survivor空間校焦。
Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱為“GC堆”统倒。
Java的普通對象存活在堆中寨典,與棧不同,堆的空間不會隨著方法調(diào)用結(jié)束而清空房匆。因此耸成,在某個方法中創(chuàng)建的對象,可以在方法調(diào)用結(jié)束之后浴鸿,繼續(xù)存在堆中井氢。這帶來的一個問題是,如果我們不斷的創(chuàng)建新的對象岳链,內(nèi)存控件將會最終消耗殆盡花竞。
OutOfMemoryError:堆中沒有內(nèi)存完成實力分配,堆也無法擴展
5.方法區(qū):
與Java堆一樣掸哑,是各個線程共享的內(nèi)存區(qū)域左胞,它用于存儲已經(jīng)被虛擬機加載的類信息、常量举户、靜態(tài)變量、即時編譯器編譯之后的代碼等數(shù)據(jù)遍烦。
注:JDK8之前俭嘁,方法區(qū)由永久代實現(xiàn),主要存放類的信息服猪、常量池供填、方法數(shù)據(jù)拐云、方法代碼等;JDK8之后近她,取消了永久代叉瘩,提出了元空間,并且常量池粘捎、靜態(tài)成員變量等遷移到了堆中薇缅;元空間不在虛擬機內(nèi)存中,而是放在本地內(nèi)存中
OutOfMemoryError:當方法區(qū)無法滿足內(nèi)存分配需求時攒磨,拋出異常
6. 運行時常量池:
運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分泳桦。Class文件中除了有類的版本、字段娩缰、方法灸撰、接口等描述信息外,還有一項信息是常量池(Constant Pool Table)拼坎,用于存放編譯期生成的各種字面量和符號引用浮毯,這部分內(nèi)容將在類加載后進入方法區(qū)的運行常量池中存放。
運行時常量池是具備動態(tài)性泰鸡,Java語言并不要求常量一定只有編譯器才能產(chǎn)生债蓝,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進入方法區(qū)運行時常量池,運行期間也可能將新的常量放入池中鸟顺,這種特性被開發(fā)人員利用比較多是String類的intern()方法惦蚊。
OutOfMemoryError:受方法區(qū)內(nèi)存的限制,常量池無法再申請到內(nèi)存時
7.直接內(nèi)存:
直接內(nèi)存(Direct Memory)并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分讯嫂,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域蹦锋,但是這部分內(nèi)存也被頻繁地使用。
在JDK 1.4中新加入了NIO類欧芽,引入了一種基于通道(Channel)與緩存區(qū)(Buffer)的I/O方式莉掂,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作千扔。這樣能在一些場景中顯著提高性能憎妙,因為避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)。
作用:NIO中可以使用Native函數(shù)庫直接分配堆外內(nèi)存曲楚,然后通過一個存儲在堆中的DirectByteBuffer對象作為內(nèi)存的引用進行操作厘唾。
OutOfMemoryError:設(shè)置虛擬機參數(shù)時可能忽略直接內(nèi)存,使得各個內(nèi)存區(qū)域總和大于物理內(nèi)存限制龙誊,動態(tài)擴展時出現(xiàn)這個異常