淺談Java內(nèi)存區(qū)域劃分和內(nèi)存分配策略
如果不知道,類的靜態(tài)變量存儲在那? 方法的局部變量存儲在那? 趕快收藏
Java內(nèi)存區(qū)域主要可以分為共享內(nèi)存,堆、方法區(qū)和線程私有內(nèi)存,虛擬機(jī)棧、本地方法棧和程序計(jì)數(shù)器。如下圖所示,本文將詳細(xì)講述各個區(qū)域,同時也會講述創(chuàng)建對象過程,內(nèi)存分配策略, 和對象訪問定位原理。覺得寫得好的,可以點(diǎn)個收藏,絕對不虧。
Java內(nèi)存區(qū)域
程序計(jì)數(shù)器
程序計(jì)數(shù)器,可以看作程序當(dāng)前線程所執(zhí)行的字節(jié)碼行號指示器。字節(jié)碼解釋器工作時就是通過改變計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理都需要依賴計(jì)數(shù)器完成。線程執(zhí)行Java方法時,記錄其正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址,線程執(zhí)行Native方法時,計(jì)數(shù)器記錄為空。程序計(jì)數(shù)器時唯一在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況區(qū)域。
理論可知,線程是通過輪流獲取CPU執(zhí)行時間以實(shí)現(xiàn)多線程的并發(fā)。為了暫停的線程下一次獲得CPU執(zhí)行時間,能正常運(yùn)行,每一個線程內(nèi)部都需要維護(hù)一個程序計(jì)數(shù)器,用來記住暫停線程暫停的位置。
注意:光理論是不夠的,在此送大家一套2020最新Java架構(gòu)實(shí)戰(zhàn)教程+大廠面試寶典,點(diǎn)擊此處 進(jìn)來獲取 一起交流進(jìn)步哦!
Java虛擬機(jī)棧
Java虛擬機(jī)棧同程序計(jì)數(shù)器一樣,也是線程私有的,虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型,每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀,用于存儲局部變量表,操作數(shù)棧、動態(tài)鏈接和方法出入口等信息。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)中入棧到出棧的過程。
本地方法棧
與虛擬機(jī)棧相似。虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。
Java堆
所有線程共享的一塊內(nèi)存區(qū)域。Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,因?yàn)樵搩?nèi)存區(qū)域的唯一目的就是存放對象實(shí)例。幾乎所有的對象實(shí)例都在這里分配內(nèi)存,同時堆也是垃圾收集器管理的主要區(qū)域。因此很多時候被稱為'GC堆'
方法區(qū)
和堆一樣,是各個線程共享的內(nèi)存區(qū)域,用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、和編譯器編譯后的代碼(也就是存儲字節(jié)碼文件.class)等數(shù)據(jù)。
方法區(qū)中有一個運(yùn)行時常量池,編譯后期生成的各種字面量和符號引用,存放在字節(jié)碼文件中的常量池中。當(dāng)類加載進(jìn)入方法區(qū)時,就會把該常量池中的內(nèi)容放入方法區(qū)中的運(yùn)行時常量池。此外也可以在程序運(yùn)行期間,將新的常量放入運(yùn)行時常量池,比如String.intern()方法,該方法先從運(yùn)行時常量池中查找是否有該值,如果有,則返回該值的引用,否則將該值加入運(yùn)行時常量池。
實(shí)例詳講
class Demo1_Car{ public static void main(String[] args) { Car c1 = new Car(); //調(diào)用屬性并賦值 c1.color = 'red'; c1.num = 8; //調(diào)用行為 c1.run(); Car c2 = new Car(); c2.color = 'black'; c2.num = 4; c2.run(); }}Class Car{ String color; int num; public void run() { System.out.println(color + '..' + num);}}
創(chuàng)建對象過程
虛擬機(jī)在遇到一條new指令時,會首先檢查這個指令的參數(shù)是否可以在方法區(qū)中定位到一個類的符號引用,并且檢查這個符號引用所代表的類是否已經(jīng)被加載,解析和初始化過。如果沒有,則必須先執(zhí)行類加載過程.
類加載完之后,需要為對象分配內(nèi)存,有兩種分配內(nèi)存的方法
指針碰撞法(要求堆內(nèi)存規(guī)整)Java堆中空閑內(nèi)存和已使用內(nèi)存分別存放在堆的兩邊,中間存放一個指針作為分界點(diǎn)的指示器,在為對象分配內(nèi)存時只需要將指針向空閑區(qū)域移動創(chuàng)建對象所需要的內(nèi)存大小即可。
空閑列表法如果堆內(nèi)存中已使用內(nèi)存區(qū)域和空閑區(qū)域相互交錯,此時虛擬機(jī)需要維護(hù)一個列表,記錄哪些內(nèi)存塊是可用的,在分配時從列表中找到一塊足夠大的內(nèi)存區(qū)域劃分給對象實(shí)例并更新列表上的記錄。
多線程情況下,線程同時分配內(nèi)存可能會造成沖突,比如使用指針碰撞法,線程A正在分配內(nèi)存,還沒有改變指針指向,線程B,又同時使用原來的指針進(jìn)行內(nèi)存分配。防止沖突有兩種方法
CAS操作:虛擬機(jī)采用CAS操作,加上失敗重試的方式保證內(nèi)存分配的原子性 本地線程分配緩沖(TLAB):預(yù)先為線程分配一部分堆內(nèi)存空間(線程私有,所以不存在同步問題)用于對象實(shí)例的內(nèi)存分配。只有當(dāng)TLAB用完,需要分配新的TLAB時,才需要進(jìn)行同步操作。內(nèi)存分配完之后,虛擬機(jī)需要將分配到的內(nèi)存空間均初始化為零值(不包括對象頭)。在虛擬機(jī)中,執(zhí)行完new指令后會接著執(zhí)行方法,把對象按照程序員的意愿進(jìn)行初始化,這樣一個真正可用的對象才算完全產(chǎn)生出來
對象在內(nèi)存中的布局
對象在內(nèi)存中的布局如下圖所示,分為對象頭、實(shí)例數(shù)據(jù)、對齊填充[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳
mark Word, 用于存儲對象自身的運(yùn)行時數(shù)據(jù),如哈希碼、GC分代年齡以及鎖狀態(tài)標(biāo)志等。類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個指針來確定這個對象是哪個類的實(shí)例。
實(shí)例數(shù)據(jù)對象真正存儲的有效信息,也是程序代碼中所定義的各種類型的字段內(nèi)容。
對齊填充并非必然存在,僅僅起著占位符的作用。
對象的訪問定位
Java程序需要通過棧上的reference數(shù)據(jù)來操作堆上的具體對象。共有兩種策略進(jìn)行對象的訪問定位
句柄訪問Java堆中劃分出一塊內(nèi)存來作為句柄池,reference中存儲的是對象的句柄地址,而句柄中包含了對象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息,需要兩次尋址。
Java堆中對象的布局中需要考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息,而reference中存儲的直接就是對象地址。
使用句柄訪問的最大好處就是reference中存儲的是穩(wěn)定的句柄地址,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中實(shí)例數(shù)據(jù)指針,而reference本身不需要修改。
問題
只需要記住一件事,就是Java對象的內(nèi)存分配均是在堆中進(jìn)行的。所以對象都存儲在堆中。
但是有人可能會懷疑方法的臨時變量不是存儲在虛擬機(jī)棧中嗎?這里我要解釋一下,虛擬機(jī)棧維護(hù)了一個局部變量表,表中存儲的是對象的引用,而真正存儲對象的地方在堆,如果局部變量都在堆里分配,那么虛擬機(jī)棧早爆滿了
同樣類的靜態(tài)變量,有人又會懷疑在方法區(qū)中存儲。其實(shí)不是的,方法區(qū)只存儲引用,具體對象是存儲在堆中的,具體實(shí)現(xiàn)可以發(fā)現(xiàn),類靜態(tài)對象是與class對象一起分配的內(nèi)存。
參考
深入理解java虛擬機(jī)
到此這篇關(guān)于淺談Java內(nèi)存區(qū)域劃分和內(nèi)存分配策略的文章就介紹到這了,更多相關(guān)Java內(nèi)存區(qū)域劃分和內(nèi)存分配內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. idea打開多個窗口的操作方法2. IntelliJ IDEA 統(tǒng)一設(shè)置編碼為utf-8編碼的實(shí)現(xiàn)3. asp知識整理筆記4(問答模式)4. 詳解idea中web.xml默認(rèn)版本問題解決5. 解決ajax的delete、put方法接收不到參數(shù)的問題方法6. IntelliJ IDEA 2020最新激活碼(親測有效,可激活至 2089 年)7. jsp EL表達(dá)式詳解8. java 優(yōu)雅關(guān)閉線程池的方案9. idea開啟代碼提示功能的方法步驟10. 使用Python爬取Json數(shù)據(jù)的示例代碼
