溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JVM對(duì)象創(chuàng)建和內(nèi)存分配原理解析

發(fā)布時(shí)間:2020-09-14 02:02:01 來源:腳本之家 閱讀:135 作者:crossoverJie 欄目:編程語言

創(chuàng)建對(duì)象

當(dāng) JVM 收到一個(gè) new 指令時(shí),會(huì)檢查指令中的參數(shù)在常量池是否有這個(gè)符號(hào)的引用,還會(huì)檢查該類是否已經(jīng)被加載過了,如果沒有的話則要進(jìn)行一次類加載。

接著就是分配內(nèi)存了,通常有兩種方式:

  • 指針碰撞
  • 空閑列表

使用指針碰撞的前提是堆內(nèi)存是完全工整的,用過的內(nèi)存和沒用的內(nèi)存各在一邊每次分配的時(shí)候只需要將指針向空閑內(nèi)存一方移動(dòng)一段和內(nèi)存大小相等區(qū)域即可。

當(dāng)堆中已經(jīng)使用的內(nèi)存和未使用的內(nèi)存互相交錯(cuò)時(shí),指針碰撞的方式就行不通了,這時(shí)就需要采用空閑列表的方式。虛擬機(jī)會(huì)維護(hù)一個(gè)空閑的列表,用于記錄哪些內(nèi)存是可以進(jìn)行分配的,分配時(shí)直接從可用內(nèi)存中直接分配即可。

堆中的內(nèi)存是否工整是有垃圾收集器來決定的,如果帶有壓縮功能的垃圾收集器就是采用指針碰撞的方式來進(jìn)行內(nèi)存分配的。

分配內(nèi)存時(shí)也會(huì)出現(xiàn)并發(fā)問題:

這樣可以在創(chuàng)建對(duì)象的時(shí)候使用 CAS 這樣的樂觀鎖來保證。

也可以將內(nèi)存分配安排在每個(gè)線程獨(dú)有的空間進(jìn)行,每個(gè)線程首先在堆內(nèi)存中分配一小塊內(nèi)存,稱為本地分配緩存(TLAB : Thread Local Allocation Buffer)。

分配內(nèi)存時(shí),只需要在自己的分配緩存中分配即可,由于這個(gè)內(nèi)存區(qū)域是線程私有的,所以不會(huì)出現(xiàn)并發(fā)問題。

可以使用 -XX:+/-UseTLAB 參數(shù)來設(shè)定 JVM 是否開啟 TLAB 。

內(nèi)存分配之后需要對(duì)該對(duì)象進(jìn)行設(shè)置,如對(duì)象頭。對(duì)象頭的一些應(yīng)用可以查看 Synchronize 關(guān)鍵字原理。

對(duì)象訪問

一個(gè)對(duì)象被創(chuàng)建之后自然是為了使用,在 Java 中是通過棧來引用堆內(nèi)存中的對(duì)象來進(jìn)行操作的。

對(duì)于我們常用的 HotSpot 虛擬機(jī)來說,這樣引用關(guān)系是通過直接指針來關(guān)聯(lián)的。

這樣的好處就是:在 Java 里進(jìn)行頻繁的對(duì)象訪問可以提升訪問速度(相對(duì)于使用句柄池來說)。

內(nèi)存分配

Eden 區(qū)分配

簡(jiǎn)單的來說對(duì)象都是在堆內(nèi)存中分配的,往細(xì)一點(diǎn)看則是優(yōu)先在 Eden 區(qū)分配。

這里就涉及到堆內(nèi)存的劃分了,為了方便垃圾回收,JVM 將對(duì)內(nèi)存分為新生代和老年代。

而新生代中又會(huì)劃分為 Eden 區(qū),from Survivor、to Survivor 區(qū)。

其中 Eden 和 Survivor 區(qū)的比例默認(rèn)是 8:1:1,當(dāng)然也支持參數(shù)調(diào)整 -XX:SurvivorRatio=8。

當(dāng)在 Eden 區(qū)分配內(nèi)存不足時(shí),則會(huì)發(fā)生 minorGC ,由于 Java 對(duì)象多數(shù)是朝生夕滅的特性,所以 minorGC 通常會(huì)比較頻繁,效率也比較高。

當(dāng)發(fā)生 minorGC 時(shí),JVM 會(huì)根據(jù)復(fù)制算法將存活的對(duì)象拷貝到另一個(gè)未使用的 Survivor 區(qū),如果 Survivor 區(qū)內(nèi)存不足時(shí),則會(huì)使用分配擔(dān)保策略將對(duì)象移動(dòng)到老年代中。

談到 minorGC 時(shí),就不得不提到 fullGC(majorGC) ,這是指發(fā)生在老年代的 GC ,不論是效率還是速度都比 minorGC 慢的多,回收時(shí)還會(huì)發(fā)生 stop the world 使程序發(fā)生停頓,所以應(yīng)當(dāng)盡量避免發(fā)生 fullGC 。

老年代分配

也有一些情況會(huì)導(dǎo)致對(duì)象直接在老年代分配,比如當(dāng)分配一個(gè)大對(duì)象時(shí)(大的數(shù)組,很長(zhǎng)的字符串),由于 Eden 區(qū)沒有足夠大的連續(xù)空間來分配時(shí),會(huì)導(dǎo)致提前觸發(fā)一次 GC,所以盡量別頻繁的創(chuàng)建大對(duì)象。

因此 JVM 會(huì)根據(jù)一個(gè)閾值來判斷大于該閾值對(duì)象直接分配到老年代,這樣可以避免在新生代頻繁的發(fā)生 GC。

對(duì)于一些在新生代的老對(duì)象 JVM 也會(huì)根據(jù)某種機(jī)制移動(dòng)到老年代中。

JVM 是根據(jù)記錄對(duì)象年齡的方式來判斷該對(duì)象是否應(yīng)該移動(dòng)到老年代,根據(jù)新生代的復(fù)制算法,當(dāng)一個(gè)對(duì)象被移動(dòng)到 Survivor 區(qū)之后 JVM 就給該對(duì)象的年齡記為1,每當(dāng)熬過一次 minorGC 后對(duì)象的年齡就 +1 ,直到達(dá)到閾值(默認(rèn)為15)就移動(dòng)到老年代中。

可以使用 -XX:MaxTenuringThreshold=15 來配置這個(gè)閾值。

總結(jié)

雖說這些內(nèi)容略顯枯燥,但當(dāng)應(yīng)用發(fā)生不正常的 GC 時(shí),可以方便更快的定位問題。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI