深入探討Java內(nèi)存區(qū)域
一、概述
Java虛擬機(jī)在執(zhí)行Java程序的過(guò)程中會(huì)把它所管理的內(nèi)存劃分為若干不同的數(shù)據(jù)區(qū)域,這些區(qū)域都有各自的用途以及創(chuàng)建和銷(xiāo)毀的時(shí)間。Java虛擬機(jī)所管理的內(nèi)存將會(huì)包括以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域,如下圖所示:
下面就每一個(gè)區(qū)域進(jìn)行闡述。
二、運(yùn)行時(shí)數(shù)據(jù)區(qū)域
程序計(jì)數(shù)器
程序計(jì)數(shù)器,可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作就是通過(guò)改變程序計(jì)數(shù)器的值來(lái)選擇下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都要依賴這個(gè)計(jì)數(shù)器來(lái)完成。
多線程中,為了讓線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各條線程之間互不影響、獨(dú)立存儲(chǔ),因此這塊內(nèi)存是 線程私有 的。
當(dāng)線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是在正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;當(dāng)執(zhí)行的是Native方法,這個(gè)計(jì)數(shù)器值為空。
此內(nèi)存區(qū)域是唯一一個(gè)沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域 。
Java虛擬機(jī)棧
Java虛擬機(jī)棧也是線程私有的 ,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈表、方法出口信息等。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過(guò)程。
局部變量表中存放了編譯器可知的各種基本數(shù)據(jù)類(lèi)型(boolean、byte、char、short、int、float、long、double)、對(duì)象引用和returnAddress類(lèi)型(指向了一條字節(jié)碼指令的地址)。
如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常。
本地方法棧
本地方法棧與虛擬機(jī)的作用相似,不同之處在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行的Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。有的虛擬機(jī)直接把本地方法棧和虛擬機(jī)棧合二為一。
會(huì)拋出stackOverflowError和OutOfMemoryError異常。
Java堆
Java堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例 。
Java堆是垃圾收集器管理的主要區(qū)域。由于現(xiàn)在收集器基本采用分代回收算法,所以Java堆還可細(xì)分為:新生代和老年代。從內(nèi)存分配的角度來(lái)看,線程共享的Java堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū)(TLAB)。
Java堆可以處于物理上不連續(xù)的內(nèi)存空間,只要邏輯上連續(xù)的即可。在實(shí)現(xiàn)上,既可以實(shí)現(xiàn)固定大小的,也可以是擴(kuò)展的。
如果堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆也無(wú)法完成擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。
方法區(qū)
方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù) 。
相對(duì)而言,垃圾收集行為在這個(gè)區(qū)域比較少出現(xiàn),但并非數(shù)據(jù)進(jìn)了方法區(qū)就永久的存在了,這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類(lèi)型的卸載,
當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分配需要時(shí),將拋出OutOfMemoryError異常。
運(yùn)行時(shí)常量池:
是方法區(qū)的一部分,它用于存放編譯期生成的各種字面量和符號(hào)引用。
直接內(nèi)存
直接內(nèi)存不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,在NIO類(lèi)中引入一種基于通道與緩沖區(qū)的IO方式,它可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。
直接內(nèi)存的分配不會(huì)受到Java堆大小的限制,但是會(huì)受到本機(jī)內(nèi)存大小的限制,所有也可能會(huì)拋OutOfMemoryError異常。
三、對(duì)象的創(chuàng)建、布局和訪問(wèn)過(guò)程
對(duì)象的創(chuàng)建
創(chuàng)建一個(gè)對(duì)象通常是需要new關(guān)鍵字,當(dāng)虛擬機(jī)遇到一條new指令時(shí),首先檢查這個(gè)指令的參數(shù)是否在常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類(lèi)是否已被加載、解析和初始化過(guò)。如果那么執(zhí)行相應(yīng)的類(lèi)加載過(guò)程。
類(lèi)加載檢查通過(guò)后,虛擬機(jī)將為新生對(duì)象分配內(nèi)存。為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來(lái)。分配的方式有兩種: 一種叫 指針碰撞 ,假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,用過(guò)的和空閑的內(nèi)存各在一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,分配內(nèi)存就是把那個(gè)指針向空閑空間的那邊挪動(dòng)一段與對(duì)象大小相等的距離。 另一種叫 空閑列表 :如果Java堆中的內(nèi)存不是規(guī)整的,虛擬機(jī)就需要維護(hù)一個(gè)列表,記錄哪個(gè)內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄。 采用哪種分配方式是由Java堆是否規(guī)整決定的,而Java堆是否規(guī)整是由所采用的垃圾收集器是否帶有壓縮整理功能決定的。 另 外一個(gè)需要考慮的問(wèn)題就是對(duì)象創(chuàng)建時(shí)的線程安全問(wèn)題,有兩種解決方案:一是對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理;另一種是吧內(nèi)存分配的動(dòng)作按照線程劃分在不 同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存(TLAB),哪個(gè)線程要分配內(nèi)存就在哪個(gè)線程的TLAB上分配,只有TLAB用完并分配 新的TLAB時(shí)才需要同步鎖定。
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間初始化為零值。這一步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就可以直接使用。
接下來(lái)虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例、如何才能找到類(lèi)的元數(shù)據(jù)信息等,這些信息存放在對(duì)象的對(duì)象頭中。
上面的工作都完成以后,從虛擬機(jī)的角度來(lái)看一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了。但是從Java程序的角度,還需要執(zhí)行init方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來(lái)。
對(duì)象的內(nèi)存布局
在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可分為三個(gè)部分: 對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。
對(duì)象頭包括兩個(gè)部分:第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC分代年齡、線程所持有的鎖等。官方稱之為“Mark Word”。第二個(gè)部分為是類(lèi)型指針,即對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例。
實(shí)例數(shù)據(jù)是對(duì)象真正存儲(chǔ)的有效信息,也是程序代碼中所定義的各種類(lèi)型的字段內(nèi)容。
對(duì)齊填充并不是必然存在的,僅僅起著占位符的作用。、Hotpot VM要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,對(duì)象頭部分正好是8字節(jié)的倍數(shù),所以當(dāng)實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí),需要通過(guò)對(duì)齊填充來(lái)對(duì)齊。
對(duì)象的訪問(wèn)定位
Java程序通過(guò)棧上的reference數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。主要的訪問(wèn)方式有使用句柄和直接指針兩種:
句柄:Java堆將會(huì)劃出一塊內(nèi)存來(lái)作為句柄池,引用中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類(lèi)型數(shù)據(jù)各自的具體地址信息 。如圖所示:
直接指針:Java堆對(duì)象的布局要考慮如何放置訪問(wèn)類(lèi)型數(shù)據(jù)的相關(guān)信息,引用中存儲(chǔ)的就是對(duì)象地址 。如圖所示:
兩個(gè)方式各有優(yōu)點(diǎn),使用句柄最大的好處是引用中存儲(chǔ)的是穩(wěn)定的句柄地址,對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中實(shí)例的地址,引用不需要修改、使用直接指針訪問(wèn)的好處是速度更快,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Spring?cloud如何實(shí)現(xiàn)FeignClient指定Zone調(diào)用
這篇文章主要介紹了Spring?cloud如何實(shí)現(xiàn)FeignClient指定Zone調(diào)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03一文掌握J(rèn)ava開(kāi)發(fā)工具M(jìn)aven(簡(jiǎn)單上手)
掌握maven的相關(guān)知識(shí)是Java開(kāi)發(fā)必備的技能,今天通過(guò)本文從入門(mén)安裝開(kāi)始,逐步深入講解maven的相關(guān)知識(shí),包括maven的安裝到簡(jiǎn)單上手maven項(xiàng)目開(kāi)發(fā),感興趣的朋友跟隨小編一起看看吧2021-06-06關(guān)于java的包Package中同名類(lèi)的沖突及其理解
這篇文章主要介紹了關(guān)于java的包Package中同名類(lèi)的沖突及其理解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08基于springMvc+hibernate的web application的構(gòu)建
下面小編就為大家?guī)?lái)一篇基于springMvc+hibernate的web application的構(gòu)建。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10SpringBoot框架RESTful接口設(shè)置跨域允許
這篇文章主要為大家詳細(xì)介紹了SpringBoot框架RESTful接口設(shè)置跨域允許,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08Java實(shí)現(xiàn)電影院訂票系統(tǒng)代碼
這篇文章主要介紹了Java實(shí)現(xiàn)電影院訂票系統(tǒng)代碼,代碼實(shí)現(xiàn)了界面類(lèi)登錄注冊(cè)類(lèi),用戶類(lèi)等,具有一定參考價(jià)值,需要的朋友可以參考下。2017-11-11java開(kāi)發(fā)SpringBoot參數(shù)校驗(yàn)過(guò)程示例教程
這篇文章主要為大家介紹了SpringBoot如何進(jìn)行參數(shù)校驗(yàn)的過(guò)程示例詳解教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10SpringBoot構(gòu)建RESTful API的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot構(gòu)建RESTful API的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Java中CyclicBarrier的理解與應(yīng)用詳解
這篇文章主要介紹了Java中CyclicBarrier的理解與應(yīng)用詳解,CyclicBarrier類(lèi)是JUC框架中的工具類(lèi),也是一個(gè)同步輔助裝置:允許多個(gè)線程去等待直到全部線程抵達(dá)了公共的柵欄點(diǎn),需要的朋友可以參考下2023-12-12