TLAB(Thread Local Allocation Buffer)

TLAB是虛擬機在堆內存的eden劃分出來的一塊專用空間,是線程專屬的。在虛擬機的TLAB功能啓動的情況下,在線程初始化時,虛擬機會爲每個線程分配一塊TLAB空間,只給當前線程使用,這樣每個線程都單獨擁有一個空間,如果需要分配內存,就在自己的空間上分配,這樣就不存在競爭的情況,可以大大提升分配效率。這裏值得注意的是,我們說TLAB是線程獨享的,但是隻是在“分配”這個動作上是線程獨享的,至於在讀取、垃圾回收等動作上都是線程共享的。而且在使用上也沒有什麼區別

也就是說,雖然每個線程在初始化時都會去堆內存中申請一塊TLAB,並不是說這個TLAB區域的內存其他線程就完全無法訪問了,其他線程的讀取還是可以的,只不過無法在這個區域中分配內存而已。

並且,在TLAB分配之後,並不影響對象的移動和回收,也就是說,雖然對象剛開始可能通過TLAB分配內存,存放在Eden區,但是還是會被垃圾回收或者被移到Survivor Space、Old Gen等。

 

還有一點需要注意的是,我們說TLAB是在eden區分配的,因爲eden區域本身就不太大,而且TLAB空間的內存也非常小,默認情況下僅佔有整個Eden空間的1%。所以,必然存在一些大對象是無法在TLAB直接分配。

遇到TLAB中無法分配的大對象,對象還是可能在eden區或者老年代等進行分配的,但是這種分配就需要進行同步控制,這也是爲什麼我們經常說:小的對象比大的對象分配起來更加高效。

 

TLAB帶來的問題

雖然在一定程度上,TLAB大大的提升了對象的分配速度,但是TLAB並不是就沒有任何問題的。

前面我們說過,因爲TLAB內存區域並不是很大,所以,有可能會經常出現不夠的情況。在《實戰Java虛擬機》中有這樣一個例子:

比如一個線程的TLAB空間有100KB,其中已經使用了80KB,當需要再分配一個30KB的對象時,就無法直接在TLAB中分配,遇到這種情況時,有兩種處理方案:

1、如果一個對象需要的空間大小超過TLAB中剩餘的空間大小,則直接在堆內存中對該對象進行內存分配。

2、如果一個對象需要的空間大小超過TLAB中剩餘的空間大小,則廢棄當前TLAB,重新申請TLAB空間再次進行內存分配。

以上兩個方案各有利弊,如果採用方案1,那麼就可能存在着一種極端情況,就是TLAB只剩下1KB,就會導致後續需要分配的大多數對象都需要在堆內存直接分配。

如果採用方案2,也有可能存在頻繁廢棄TLAB,頻繁申請TLAB的情況,而我們知道,雖然在TLAB上分配內存是線程獨享的,但是TLAB內存自己從堆中劃分出來的過程確實可能存在衝突的,所以,TLAB的分配過程其實也是需要併發控制的。而頻繁的TLAB分配就失去了使用TLAB的意義。

爲了解決這兩個方案存在的問題,虛擬機定義了一個refill_waste的值,這個值可以翻譯爲“最大浪費空間”。

當請求分配的內存大於refill_waste的時候,會選擇在堆內存中分配。若小於refill_waste值,則會廢棄當前TLAB,重新創建TLAB進行對象內存分配。

前面的例子中,TLAB總空間100KB,使用了80KB,剩餘20KB,如果設置的refill_waste的值爲25KB,那麼如果新對象的內存大於25KB,則直接堆內存分配,如果小於25KB,則會廢棄掉之前的那個TLAB,重新分配一個TLAB空間,給新對象分配內存。

 

TLAB使用的相關參數

TLAB功能是可以選擇開啓或者關閉的,可以通過設置-XX:+/-UseTLAB參數來指定是否開啓TLAB分配。

TLAB默認是eden區的1%,可以通過選項-XX:TLABWasteTargetPercent設置TLAB空間所佔用Eden空間的百分比大小。

默認情況下,TLAB的空間會在運行時不斷調整,使系統達到最佳的運行狀態。如果需要禁用自動調整TLAB的大小,可以使用-XX:-ResizeTLAB來禁用,並且使用-XX:TLABSize來手工指定TLAB的大小。

TLAB的refill_waste也是可以調整的,默認值爲64,即表示使用約爲1/64空間大小作爲refill_waste,使用參數:-XX:TLABRefillWasteFraction來調整。

如果想要觀察TLAB的使用情況,可以使用參數-XX+PringTLAB 進行跟蹤。

 

總結

爲了保證對象的內存分配過程中的線程安全性,HotSpot虛擬機提供了一種叫做TLAB(Thread Local Allocation Buffer)的技術。

在線程初始化時,虛擬機會爲每個線程分配一塊TLAB空間,只給當前線程使用,當需要分配內存時,就在自己的空間上分配,這樣就不存在競爭的情況,可以大大提升分配效率。

所以,“堆是線程共享的內存區域”這句話並不完全正確,因爲TLAB是堆內存的一部分,它在讀取上確實是線程共享的,但是在內存分配上,是線程獨享的。

TLAB的空間其實並不大,所以大對象還是可能需要在堆內存中直接分配。那麼,對象的內存分配步驟就是先嚐試TLAB分配,空間不足之後,再判斷是否應該直接進入老年代,然後再確定是再eden分配還是在老年代分配。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章