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