JVM 的棧上分配、TLAB、PLAB 有啥區(qū)別?
?大家好,我是樹哥。
我們?cè)趯W(xué)習(xí) G1 回收器的時(shí)候,一般我們都會(huì)接觸到 TLAB 和 PLAB 這兩個(gè)術(shù)語。它們都是為了提高內(nèi)存分配效率而存在的,但它們和棧上分配有什么區(qū)別呢?今天,就讓樹哥帶著大家盤一盤。
棧上分配
稍微了解過 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è)線程都有自己的棧,棧信息無法在線程之間共享。
一般情況下,每個(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ān)系就像是列車的中央調(diào)度室和火車的關(guān)系。每次線程需要分配內(nèi)存空間,都需要去到堆去申請(qǐng)空間,會(huì)耗費(fèi)不少時(shí)間和精力。
這個(gè)時(shí)候有人就發(fā)現(xiàn),線程的有些對(duì)象其實(shí)別人也不會(huì)訪問到,放在堆中貌似也沒什么大作用。于是他提出:對(duì)于這些其他線程不會(huì)訪問的對(duì)象,我們能不能讓線程自己分配在它自己的??臻g上?這樣不就可以節(jié)省不少交互時(shí)間了么!
這個(gè)方法確實(shí)不錯(cuò),如果能實(shí)現(xiàn)應(yīng)該可以提高對(duì)象創(chuàng)建的時(shí)間,提高虛擬機(jī)的運(yùn)行效率。
但問題是:我怎么知道哪些對(duì)象可以分配在棧上,哪些不行呢?
其實(shí)聰明的軟件工程師們?cè)缇徒鉀Q了這個(gè)問題了,他們新造了一個(gè)名字:逃逸分析。
那么什么是逃逸分析呢?
從字面意思上來講,逃逸分析的目的是判斷對(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 是類的成員變量,可以被任何線程訪問,因此它屬于逃逸對(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ì)被其他線程訪問到,也不會(huì)訪問。所以該 user 實(shí)例對(duì)象的作用域只在該函數(shù)中,因此它并未發(fā)生逃逸。對(duì)于這樣的情況,虛擬機(jī)就有可能將其分配在棧上,而不在堆上。
看到這里,我相信許多人都應(yīng)該明白了什么是棧上分配了。簡(jiǎn)單點(diǎn)說,就是將本來應(yīng)該分配在堆中的對(duì)象,讓其分配在線程私有的棧上。通過這種方式,減少垃圾回收的壓力,提高虛擬機(jī)的運(yùn)行效率。
TLAB
TLAB(Thread Local Allocation Buffer),即線程本地分配緩存。這是一塊線程專用的內(nèi)存分配區(qū)域,TLAB 占用的是 eden 區(qū)的空間。在 TLAB 啟用的情況下(默認(rèn)開啟),JVM 會(huì)為每一個(gè)線程分配一塊 TLAB 區(qū)域。
那么問什么需要 TLAB 呢?這是為了加速對(duì)象的分配!
由于對(duì)象一般分配在堆上,而堆事線程共用的,因此可能會(huì)有多個(gè)線程在堆上申請(qǐng)空間,而每一次的對(duì)象分配都必須線程同步,這樣會(huì)降低內(nèi)存分配的效率。
考慮到對(duì)象分配是非常常見的操作,于是 JVM 使用 TLAB 這樣的線程轉(zhuǎn)悠區(qū)域來避免多線程沖突,提高對(duì)象分配效率。
為了不至于導(dǎo)致 Eden 區(qū)被填充滿,因此 TLAB 空間一般不會(huì)太大。因此大對(duì)象有可能無法在 TLAB 分配,只能直接分配到堆上。這其實(shí)是一種折中的設(shè)計(jì)哲學(xué),因?yàn)榇蠖鄶?shù)分配的對(duì)象都比較小,因此 TLAB 空間能滿足大多數(shù)的需求。
PLAB
PLAB(Promotion Local Allocation Buffers),即晉升本地分配緩存。PLAB 的作用于 TLAB 類似,都是為了加速對(duì)象分配效率,避免多線程競(jìng)爭(zhēng)而誕生的。 只不過 PLAB 是應(yīng)用于對(duì)象晉升到 Survivor 區(qū)或老年代。與 TLAB 類似,每個(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ū)分配,如下圖所示。
圖片來自網(wǎng)絡(luò)
總結(jié)
了解完棧上分配、TLAB、PLAB 之后,我們基本上可以清晰地回答如下問題。
什么是棧上分配,它解決什么問題?
棧上分配指的是對(duì)象直接在線程棧幀中進(jìn)行分配,而不在堆中分配。它主要是為了解決多線程對(duì)象分配的低效問題,通過在棧上分配內(nèi)存,避免了多線程之間的沖突,提高了對(duì)象的分配效率。但要注意的是,其只能分配較小對(duì)象,并且該對(duì)象必須不被其他對(duì)象線程引用。
什么是 TLAB,它解決什么問題?
TLAB 指的是線程本地分配緩存,其對(duì)應(yīng) Eden 區(qū)的某個(gè)區(qū)域,但這塊區(qū)域只可以被該線程使用。
棧上分配和 TLAB 有啥區(qū)別?
TLAB 可以理解成是棧上分配的升級(jí)版本。棧上分配的對(duì)象只能被線程本身訪問,但 TLAB 的對(duì)象可以被其他對(duì)象讀取,但應(yīng)該無法操作。通過 TLAB,它解決了部分需要多線程訪問的對(duì)象分配效率問題,進(jìn)一步提升了 JVM 的對(duì)象分配效率。
什么是 PLAB,它解決了什么問題?
PLAB 是為了在對(duì)象晉升到 Survivor 區(qū)或老年代的時(shí)候,提升對(duì)象的分配效率。其優(yōu)化思路與 TLAB 類似,只是應(yīng)用的地方不同。
參考資料
JVM 對(duì)象分配之棧上分配 & TLAB 分配 - 掘金
棧上分配技術(shù),這么高端的技術(shù)到底是啥?
JVM 內(nèi)存分配機(jī)制之棧上分配與 TLAB 的區(qū)別 - 騰訊云開發(fā)者社區(qū) - 騰訊云?