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