全稱是 Thread Local Allocation Buffer,即線程本地分配緩存,是一個線程專用的內存分配區域。
一、Java對象的內存分配過程如何保證線程安全的?
因為堆是線程之間共享的,如果在並發場景中,兩個線程先后把對象的引用指向了同一個內存區域,怎么辦?
為了解決這個並發問題,對象的內存分配過程就必須進行同步控制,但是,無論使用哪種方案(有可能是CAS),都會影響內存的分配效率。然而對於 Java 來說對象的分配是高頻操作。
由此 HotSpot 虛擬機采用了這個方案:每個線程在 Java 堆中預先分配一小塊內存,然后在給對象分配內存的時候,直接在自己的這塊”私有“內存中進行分配,當這部分用完之后,再分配新的”私有“內存。
這種方案被稱之為 TLAB 分配。這部分 buffer 是從堆中划分出來的,但是本地線程獨享的。
二、什么是 TLAB
TLAB 是虛擬機在內存的 eden 區划分出來的一塊專用空間,是線程專屬的。在啟用 TLAB 的情況下,當線程被創建時,虛擬機會為每個線程分配一塊 TLAB 空間,只給當前線程使用,這樣每個線程都單獨擁有一個空間,如果需要分配內存,就在自己的空間上分配,這樣就不存在競爭的情況,可以大大提高分配效率。
所以說,因為有了 TLAB 技術,堆內存並不是完完全全的線程共享,其中 eden 區中還是有一部分空間是分配給線程獨享的。
注意:這里 TLAB 的線程獨享是針對於分配動作,至於讀取、垃圾回收等工作是線程共享的,而且在使用上也沒什么區別。
也就是說,雖然每個線程在初始化時都會去堆內存中申請一塊 TLAB,並不是說這個 TLAB 區域的內存其他線程就完全無法訪問了,其他線程的讀取還是可以的,只不過無法在這個區域中分配內存而已。
並且,在 TLAB 分配之后,並不影響對象的移動和回收,也就是說,雖然對象剛開始可能通過 TLAB 分配內存,存放在 Eden 區,但是還是會被垃圾回收或者被移到 S 區和老年代等。
還有一點需要注意的是,我們說 TLAB 是在 eden 區分配的,因為 eden 區域本身就不太大,而且 TLAB 空間的內存也非常小,默認情況下僅占有整個 eden 空間的 1%。所以,必然存在一些大對象是無法在 TLAB 直接分配。遇到 TLAB 中無法分配的大對象,對象還是可能在 eden 區或者老年代等進行分配的,但是這種分配就需要進行同步控制,這也是為什么我們經常說:小的對象比大的對象分配起來更加高效。
三、TLAB 帶來的問題
主要問題就是因為 TLAB 空間太小導致的。
比如一個線程的 TLAB 空間有 100KB,其中已經使用了 80KB,當需要再分配一個 30KB 的對象時,就無法直接在 TLAB 中分配,遇到這種情況時有兩種處理方案:
- 直接在堆內存中對該對象進行內存分配。
- 廢棄當前的 TLAB,重新申請 TLAB 空間再次進行內存分配。
方案 1 的話,如果 TLAB 只剩下 1KB 的空間了,那么后續的大多數對象都需要在堆內存中分配,方案 2 的話,有可能會有頻繁的廢棄 TLAB 申請 TLAB 的情況。TLAB 內存自己從堆中進行分配時也是需要並發控制的,而頻繁的分配 TLAB 就失去了 TLAB 的意義了。
為了解決這個問題,虛擬機定義了一個 refill_waste 的值,這個值可以翻譯為”最大浪費空間“。
當 TLAB 剩余空間不足時,
- 若請求分配的內存大於 refill_waste,會選擇在堆內存中分配。
- 若請求分配的內存小於 refill_waste,會選擇廢棄當前的 TLAB,重新創建 TLAB 進行對象內存分配。
前面的例子中,TLAB總空間100KB,使用了80KB,剩余20KB,如果設置的refill_waste的值為25KB,那么如果新對象的內存大於25KB,則直接堆內存分配,如果小於25KB,則會廢棄掉之前的那個TLAB,重新分配一個TLAB空間,給新對象分配內存。
四、相關參數
主要有三個參數:
- -XX:+/-UseTLAB 用來控制是否開啟 TLAB。
- -XX:TLABWasteTargetPercent 設置 TLAB 空間占用 Eden 區的百分比大小,默認是 1%。
- -XX:-ResizeTLAB 禁用自動調整 TLAB 的大小。默認情況下,TLAB的空間會在運行時不斷調整,使系統達到最佳的運行狀態。
- -XX:TLABSize 手動指定 TLAB 的大小。
- -XX:TLABRefillWasteFraction 調整 refill_waste 參數,默認是64,表示使用 1/64 的 TALB 空間作為 refill_waste 的值。
- -XX:+PrintTLAB 跟蹤 TLAB 的使用情況。