JVM 的棧上分配、TLAB、PLAB 有啥區(qū)別?
?大家好,我是樹(shù)哥。
我們?cè)趯W(xué)習(xí) G1 回收器的時(shí)候,一般我們都會(huì)接觸到 TLAB 和 PLAB 這兩個(gè)術(shù)語(yǔ)。它們都是為了提高內(nèi)存分配效率而存在的,但它們和棧上分配有什么區(qū)別呢?今天,就讓樹(shù)哥帶著大家盤(pán)一盤(pán)。

棧上分配
稍微了解過(guò) Java 虛擬機(jī)內(nèi)存結(jié)構(gòu)的同學(xué)都知道,在 Java 虛擬機(jī)中有兩個(gè)關(guān)鍵的存儲(chǔ)數(shù)據(jù)節(jié)點(diǎn),那就是:堆與棧。
其中堆是所有線程共享的一塊內(nèi)存,幾乎所有對(duì)象的分配都在這塊內(nèi)存中。而棧則是線程自己私有的,只存儲(chǔ)線程自己的局部變量等信息。每個(gè)線程都有自己的棧,棧信息無(wú)法在線程之間共享。

一般情況下,每個(gè)線程如果有新建的對(duì)象,那么會(huì)跟 JVM 申請(qǐng)?jiān)诙焉蟿?chuàng)建對(duì)應(yīng)的對(duì)象,而線程的棧則存儲(chǔ)了指向堆對(duì)象的指針。每當(dāng)一個(gè)線程想創(chuàng)建一個(gè)對(duì)象時(shí),首先會(huì)請(qǐng)求 JVM,之后 JVM 進(jìn)行協(xié)調(diào),創(chuàng)建完成之后再告訴線程,線程最后將引用放到棧中。
在對(duì)象創(chuàng)建的這個(gè)過(guò)程,堆和棧之間的關(guān)系就像是列車(chē)的中央調(diào)度室和火車(chē)的關(guān)系。每次線程需要分配內(nèi)存空間,都需要去到堆去申請(qǐng)空間,會(huì)耗費(fèi)不少時(shí)間和精力。
這個(gè)時(shí)候有人就發(fā)現(xiàn),線程的有些對(duì)象其實(shí)別人也不會(huì)訪問(wèn)到,放在堆中貌似也沒(méi)什么大作用。于是他提出:對(duì)于這些其他線程不會(huì)訪問(wèn)的對(duì)象,我們能不能讓線程自己分配在它自己的棧空間上?這樣不就可以節(jié)省不少交互時(shí)間了么!
這個(gè)方法確實(shí)不錯(cuò),如果能實(shí)現(xiàn)應(yīng)該可以提高對(duì)象創(chuàng)建的時(shí)間,提高虛擬機(jī)的運(yùn)行效率。
但問(wèn)題是:我怎么知道哪些對(duì)象可以分配在棧上,哪些不行呢?
其實(shí)聰明的軟件工程師們?cè)缇徒鉀Q了這個(gè)問(wèn)題了,他們新造了一個(gè)名字:逃逸分析。
那么什么是逃逸分析呢?
從字面意思上來(lái)講,逃逸分析的目的是判斷對(duì)象的作用域是否有可能逃出函數(shù)體。例如下面的代碼就顯示了一個(gè)逃逸的對(duì)象:
private static User user;
private static void hello(){
u = new User();
u.name = "java.top.select";
u.website = "http://www.shuyi.me";
}
對(duì)象實(shí)例 user 是類(lèi)的成員變量,可以被任何線程訪問(wèn),因此它屬于逃逸對(duì)象。但如果我們將代碼稍微改動(dòng)一下,該對(duì)象就可以線程非逃逸的了。
private static void hello(){
User u = new User();
u.name = "java.top.select";
u.website = "http://www.shuyi.me";
}
可以看到 user 實(shí)例作用域只在 hello 函數(shù)中,不會(huì)被其他線程訪問(wèn)到,也不會(huì)訪問(wèn)。所以該 user 實(shí)例對(duì)象的作用域只在該函數(shù)中,因此它并未發(fā)生逃逸。對(duì)于這樣的情況,虛擬機(jī)就有可能將其分配在棧上,而不在堆上。
看到這里,我相信許多人都應(yīng)該明白了什么是棧上分配了。簡(jiǎn)單點(diǎn)說(shuō),就是將本來(lái)應(yīng)該分配在堆中的對(duì)象,讓其分配在線程私有的棧上。通過(guò)這種方式,減少垃圾回收的壓力,提高虛擬機(jī)的運(yùn)行效率。
TLAB
TLAB(Thread Local Allocation Buffer),即線程本地分配緩存。這是一塊線程專(zhuān)用的內(nèi)存分配區(qū)域,TLAB 占用的是 eden 區(qū)的空間。在 TLAB 啟用的情況下(默認(rèn)開(kāi)啟),JVM 會(huì)為每一個(gè)線程分配一塊 TLAB 區(qū)域。
那么問(wèn)什么需要 TLAB 呢?這是為了加速對(duì)象的分配!
由于對(duì)象一般分配在堆上,而堆事線程共用的,因此可能會(huì)有多個(gè)線程在堆上申請(qǐng)空間,而每一次的對(duì)象分配都必須線程同步,這樣會(huì)降低內(nèi)存分配的效率。
考慮到對(duì)象分配是非常常見(jiàn)的操作,于是 JVM 使用 TLAB 這樣的線程轉(zhuǎn)悠區(qū)域來(lái)避免多線程沖突,提高對(duì)象分配效率。
為了不至于導(dǎo)致 Eden 區(qū)被填充滿,因此 TLAB 空間一般不會(huì)太大。因此大對(duì)象有可能無(wú)法在 TLAB 分配,只能直接分配到堆上。這其實(shí)是一種折中的設(shè)計(jì)哲學(xué),因?yàn)榇蠖鄶?shù)分配的對(duì)象都比較小,因此 TLAB 空間能滿足大多數(shù)的需求。
PLAB
PLAB(Promotion Local Allocation Buffers),即晉升本地分配緩存。PLAB 的作用于 TLAB 類(lèi)似,都是為了加速對(duì)象分配效率,避免多線程競(jìng)爭(zhēng)而誕生的。 只不過(guò) PLAB 是應(yīng)用于對(duì)象晉升到 Survivor 區(qū)或老年代。與 TLAB 類(lèi)似,每個(gè)線程都有獨(dú)立的 PLAB 區(qū)。
對(duì)象內(nèi)存分配流程
對(duì)于棧上分配與 TLAB 而言,其是有一定關(guān)系的。在進(jìn)行對(duì)象內(nèi)存分配的時(shí)候,首先會(huì)嘗試進(jìn)行棧上分配,接著嘗試進(jìn)行 TLAB 分配,接著判斷是否可以直接進(jìn)入老年代,最后不行的話再在 eden 區(qū)分配,如下圖所示。

圖片來(lái)自網(wǎng)絡(luò)
總結(jié)
了解完棧上分配、TLAB、PLAB 之后,我們基本上可以清晰地回答如下問(wèn)題。
什么是棧上分配,它解決什么問(wèn)題?
棧上分配指的是對(duì)象直接在線程棧幀中進(jìn)行分配,而不在堆中分配。它主要是為了解決多線程對(duì)象分配的低效問(wèn)題,通過(guò)在棧上分配內(nèi)存,避免了多線程之間的沖突,提高了對(duì)象的分配效率。但要注意的是,其只能分配較小對(duì)象,并且該對(duì)象必須不被其他對(duì)象線程引用。
什么是 TLAB,它解決什么問(wèn)題?
TLAB 指的是線程本地分配緩存,其對(duì)應(yīng) Eden 區(qū)的某個(gè)區(qū)域,但這塊區(qū)域只可以被該線程使用。
棧上分配和 TLAB 有啥區(qū)別?
TLAB 可以理解成是棧上分配的升級(jí)版本。棧上分配的對(duì)象只能被線程本身訪問(wèn),但 TLAB 的對(duì)象可以被其他對(duì)象讀取,但應(yīng)該無(wú)法操作。通過(guò) TLAB,它解決了部分需要多線程訪問(wèn)的對(duì)象分配效率問(wèn)題,進(jìn)一步提升了 JVM 的對(duì)象分配效率。
什么是 PLAB,它解決了什么問(wèn)題?
PLAB 是為了在對(duì)象晉升到 Survivor 區(qū)或老年代的時(shí)候,提升對(duì)象的分配效率。其優(yōu)化思路與 TLAB 類(lèi)似,只是應(yīng)用的地方不同。
參考資料
JVM 對(duì)象分配之棧上分配 & TLAB 分配 - 掘金
棧上分配技術(shù),這么高端的技術(shù)到底是啥?
JVM 內(nèi)存分配機(jī)制之棧上分配與 TLAB 的區(qū)別 - 騰訊云開(kāi)發(fā)者社區(qū) - 騰訊云?















 
 
 






 
 
 
 