棧上分配
1. 出現(xiàn)原因
Java堆中內(nèi)存是線程共享的催首,假設(shè)所有對象都從堆中分配的話,所有回收對象的篩選泄鹏、整理郎任、清除都需要耗費(fèi)大量的資源,十分不合理备籽,那么對象分配在棧幀中舶治,隨棧而生,那么GC回收的所耗費(fèi)的資源就可以省略车猬,大大提高JVM的效率霉猛。
2. 技術(shù)支持
逃逸分析:在編程語言的編譯優(yōu)化原理中,分析指針動態(tài)范圍的方法稱之為逃逸分析珠闰。它跟靜態(tài)代碼分析技術(shù)中的指針分析和外形分析類似韩脏。通俗一點講,當(dāng)一個對象的指針被多個方法或線程引用時铸磅,我們稱這個指針發(fā)生了逃逸。
方法逃逸:例如調(diào)用參數(shù)傳遞到其他方法中杭朱。
線程逃逸:當(dāng)前線程對象被其他線程訪問阅仔。
總結(jié):假設(shè)對象沒有逃逸,那么允許將對象打散分配在棧上弧械。JVM允許將線程私有的對象打散分配在棧上八酒,而不是分配在堆上。分配在棧上的好處是可以在函數(shù)調(diào)用結(jié)束后自行銷毀刃唐,而不需要垃圾回收器的介入羞迷,從而提高系統(tǒng)性能。
TLAB分配
TLAB全稱是Thread Local Allocation Buffer画饥,即線程本地分配緩存區(qū)衔瓮。由于對象一般分配在堆內(nèi)存中,堆是線程共享的抖甘,每次對象分配都會進(jìn)行線程同步热鞍,在多線程情況下同步操作是會讓分配效率大大降低。JVM使用TLAB來避免線程間的沖突,從而提高分配效率薇宠。
TLAB本身占用Eden空間偷办,在開啟TLAB的情況下,JVM會為每條線程分配一塊TLAB的空間澄港。參數(shù)-XX:+TLAB開啟TLAB椒涯,默認(rèn)開啟。TLAB內(nèi)存占用非常小回梧,默認(rèn)是Eden的1%废岂,也可以使用-XX:TLABWasteTargetPercent設(shè)置TLAB空間所占用Eden的百分比大小。
由于TLAB空間一般不會很大漂辐,因此大對象無法在TLAB中進(jìn)行分配泪喊,總是會直接分配在堆內(nèi)存中。TLAB空間由于比較小髓涯,因此很容易裝滿袒啼。比如,一個100K的空間纬纪,已經(jīng)使用了80KB蚓再,當(dāng)需要再分配一個30KB的對象時,肯定就無能為力了包各。這時虛擬機(jī)會有兩種選擇摘仅,第一,廢棄當(dāng)前TLAB问畅,這樣就會浪費(fèi)20KB空間娃属;第二,將這30KB的對象直接分配在堆上护姆,保留當(dāng)前的TLAB矾端,這樣可以希望將來有小于20KB的對象分配請求可以直接使用這塊空間。實際上虛擬機(jī)內(nèi)部會維護(hù)一個叫作refill_waste的值卵皂,當(dāng)請求對象大于refill_waste時秩铆,會選擇在堆中分配,若小于該值灯变,則會廢棄當(dāng)前TLAB殴玛,新建TLAB來分配對象。這個閾值可以使用TLABRefillWasteFraction來調(diào)整添祸,它表示TLAB中允許產(chǎn)生這種浪費(fèi)的比例滚粟。默認(rèn)值為64,即表示使用約為1/64的TLAB空間作為refill_waste刃泌。默認(rèn)情況下坦刀,TLAB和refill_waste都會在運(yùn)行時不斷調(diào)整的愧沟,使系統(tǒng)的運(yùn)行狀態(tài)達(dá)到最優(yōu)。如果想要禁用自動調(diào)整TLAB的大小鲤遥,可以使用-XX:-ResizeTLAB禁用ResizeTLAB沐寺,并使用-XX:TLABSize手工指定一個TLAB的大小。
-XX:+PrintTLAB可以跟蹤TLAB的使用情況盖奈。一般不建議手工修改TLAB相關(guān)參數(shù)混坞,推薦使用虛擬機(jī)默認(rèn)行為。
內(nèi)存分配
為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來钢坦,有兩種方法究孕。
指針碰撞
內(nèi)存的使用與未使用由指針作為分界線,指導(dǎo)指針與內(nèi)存最大值地址重合則內(nèi)存分配完成爹凹。針對規(guī)整的內(nèi)存分配厨诸,適用于復(fù)制算法和標(biāo)記整理算法的GC收集器,例如Serial禾酱、ParNew微酬、Parallel、G1颤陶。
空閑列表
JVM維護(hù)一個列表颗管,記錄內(nèi)存的使用情況,根據(jù)對象的大小分配內(nèi)存滓走,并更新列表垦江。針對不規(guī)整的內(nèi)存分配,適用于標(biāo)記清除算法的GC收集器搅方,例如CMS比吭。