Java對象分配策略(2)

分配流程:



在Java中,典型的對象不在堆上分配的情況有兩種:TLAB棧上分配(嚴(yán)格來說TLAB也是屬于堆,只是在TLAB比較特殊)。

一 ?棧上分配

JVM在Server模式下的逃逸分析可以分析出某個(gè)對象是否永遠(yuǎn)只在某個(gè)方法、線程的范圍內(nèi),并沒有“逃逸”出這個(gè)范圍,逃逸分析的一個(gè)結(jié)果就是對于某些未逃逸對象可以直接在棧上分配,由于該對象一定是局部的,所以棧上分配不會(huì)有問題。

在實(shí)際的應(yīng)用程序,尤其是大型程序中反而發(fā)現(xiàn)實(shí)施逃逸分析可能出現(xiàn)效果不穩(wěn)定的情況,或因分析過程耗時(shí)但卻無法有效判別出非逃逸對象而導(dǎo)致性能(即時(shí)編譯的收益)有所下降,所以在很長的一段時(shí)間里,即使是Server Compiler,也默認(rèn)不開啟逃逸分析,甚至在某些版本(如JDK 1.6 Update18)中還曾經(jīng)短暫地完全禁止了這項(xiàng)優(yōu)化。


二 ?TLAB分配

對象創(chuàng)建在虛擬機(jī)中是非常頻繁的行為,即使是僅僅修改一個(gè)指針?biāo)赶虻奈恢茫诓l(fā)情況下也并不是線程安全的,可能出現(xiàn)正在給對象A分配內(nèi)存,指針還沒來得及修改,對象B又同時(shí)使用了原來的指針來分配內(nèi)存的情況。

解決這個(gè)問題有兩種方案,一種是對分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理——實(shí)際上虛擬機(jī)采用CAS和失敗重試的方式保證更新操作的原子性;另一種是把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer, TLAB)。

JVM在內(nèi)存新生代Eden Space中開辟了一小塊區(qū)域,由線程私有,稱作TLAB(Thread-local allocation buffer),默認(rèn)設(shè)定為占用Eden Space的1%。在Java程序中很多對象都是小對象且用過即丟,它們不存在線程共享也適合被快速GC,所以對于小對象通常JVM會(huì)優(yōu)先分配在TLAB上,并且TLAB上的分配由于是線程私有所以沒有鎖開銷。因此在實(shí)踐中分配多個(gè)小對象的效率通常比分配一個(gè)大對象的效率要高。

哪個(gè)線程要分配內(nèi)存,就在哪個(gè)線程的TLAB上分配,只有TLAB用完并分配新的TLAB時(shí),才需要同步鎖定。虛擬機(jī)是否使用TLAB,可以通過-XX:+/-UseTLAB參數(shù)來設(shè)定。通常默認(rèn)的TLAB區(qū)域大小是Eden區(qū)域的1%,當(dāng)然也可以手工進(jìn)行調(diào)整,對應(yīng)的JVM參數(shù)是-XX:TLABWasteTargetPercent。


三 ?為什么不直接在堆上分配

我們知道堆是由所有線程共享的,既然如此那它就是競爭資源,對于競爭資源,必須采取必要的同步,所以當(dāng)使用new關(guān)鍵字在堆上分配對象時(shí),是需要鎖的。既然有鎖,就必定存在鎖帶來的開銷,而且由于是對整個(gè)堆加鎖,相對而言鎖的粒度還是比較大的,影響效率。而無論是TLAB還是棧都是線程私有的,私有即避免了競爭。

所以對于某些特殊情況,可以采取避免在堆上分配對象的辦法,以提高對象創(chuàng)建和銷毀的效率。


四? 對象內(nèi)存分配的兩種方法

為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來。

1) 指針碰撞(Serial、ParNew等帶Compact過程的收集器)

假設(shè)Java堆中內(nèi)存是絕對規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。

2)空閑列表(CMS這種基于Mark-Sweep算法的收集器)

如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒有辦法簡單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對象實(shí)例,并更新列表上的記錄,這種分配方式稱為“空閑列表”(Free List)。

選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。因此,在使用Serial、ParNew等帶Compact過程的收集器時(shí),系統(tǒng)采用的分配算法是指針碰撞,而使用CMS這種基于Mark-Sweep算法的收集器時(shí),通常采用空閑列表。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容