全稱是 Thread Local Allocation Buffer,即線程本地分配緩存,是一個線程專用的內存分配區域。
1、Java對象的內存分配過程如何保證線程安全的?
由於堆是線程之間共享的,若是在併發場景中,兩個線程前後把對象的引用指向了同一個內存區域,怎麼辦?
爲了解決這個併發問題,對象的內存分配過程就必須進行同步控制,可是,不管使用哪一種方案(有多是CAS),都會影響內存的分配效率。然而對於 Java 來講對象的分配是高頻操做。
由此 HotSpot 虛擬機採用了這個方案:每一個線程在 Java 堆中預先分配一小塊內存,而後在給對象分配內存的時候,直接在本身的這塊」私有「內存中進行分配,當這部分用完以後,再分配新的」私有「內存。
這種方案被稱之爲 TLAB 分配。這部分 buffer 是從堆中劃分出來的,可是本地線程獨享的。
2、什麼是 TLAB
TLAB 是虛擬機在內存的 eden 區劃分出來的一塊專用空間,是線程專屬的。在啓用 TLAB 的狀況下,當線程被建立時,虛擬機會爲每一個線程分配一塊 TLAB 空間,只給當前線程使用,這樣每一個線程都單獨擁有一個空間,若是須要分配內存,就在本身的空間上分配,這樣就不存在競爭的狀況,能夠大大提升分配效率。
因此說,由於有了 TLAB 技術,堆內存並非完徹底全的線程共享,其中 eden 區中仍是有一部分空間是分配給線程獨享的。
注意:這裏 TLAB 的線程獨享是針對於分配動做,至於讀取、垃圾回收等工做是線程共享的,並且在使用上也沒什麼區別。
也就是說,雖然每一個線程在初始化時都會去堆內存中申請一塊 TLAB,並非說這個 TLAB 區域的內存其餘線程就徹底沒法訪問了,其餘線程的讀取仍是能夠的,只不過沒法在這個區域中分配內存而已。
而且,在 TLAB 分配以後,並不影響對象的移動和回收,也就是說,雖然對象剛開始可能經過 TLAB 分配內存,存放在 Eden 區,可是仍是會被垃圾回收或者被移到 S 區和老年代等。
還有一點須要注意的是,咱們說 TLAB 是在 eden 區分配的,由於 eden 區域自己就不太大,並且 TLAB 空間的內存也很是小,默認狀況下僅佔有整個 eden 空間的 1%。因此,必然存在一些大對象是沒法在 TLAB 直接分配。遇到 TLAB 中沒法分配的大對象,對象仍是可能在 eden 區或者老年代等進行分配的,可是這種分配就須要進行同步控制,這也是爲何咱們常常說:小的對象比大的對象分配起來更加高效。
3、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空間,給新對象分配內存。
4、相關參數
主要有三個參數:
- -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 的使用狀況。