首先看一個問題:Java對象的內(nèi)存分配過程是如何保證線程安全的大磺?
對象的內(nèi)存分配過程中贡歧,主要是對象的引用指向這個內(nèi)存區(qū)域滩租,然后進行初始化操作。
但是艘款,因為堆是全局共享的持际,因此在同一時間,可能有多個線程在堆上申請空間哗咆,在并發(fā)場景中蜘欲,就會存在兩個線程先后把對象引用指向了同一個內(nèi)存區(qū)域。如圖:
為了解決這個并發(fā)問題晌柬,對象的內(nèi)存分配過程就必須進行同步控制姥份。但是無論是使用哪種同步方案(實際上虛擬機使用的可能是CAS),都會影響內(nèi)存的分配效率年碘。所以就有了一個HotSpot虛擬機的解決方案澈歉,這種方案被稱之為TLAB分配,即
Thread Local Allocation Buffer
屿衅。這部分Buffer是從堆中劃分出來的埃难,但是是本地線程獨享的。TLAB只是HotSpot虛擬機的一個優(yōu)化方案涤久,不代表所有的虛擬機都有這個特性涡尘。
每個線程在Java堆中預(yù)先分配一小塊內(nèi)存,然后再給對象分配內(nèi)存的時候响迂,直接在自己這塊”私有”內(nèi)存中分配考抄,當(dāng)這部分區(qū)域用完之后,再分配新的”私有”內(nèi)存蔗彤。
1. 什么是TLAB
TLAB是虛擬機在堆內(nèi)存的eden劃分出來的一塊專用空間川梅,是線程專屬的。在虛擬機的TLAB功能啟動的情況下然遏,在線程初始化時贫途,虛擬機會為每個線程分配一塊TLAB空間,只給當(dāng)前線程使用待侵,這樣每個線程都單獨擁有一個空間潮饱,如果需要分配內(nèi)存,就在自己的空間上分配诫给,這樣就不存在競爭的情況香拉,可以大大提升分配效率啦扬。
所以說,因為有了TLAB技術(shù)凫碌,堆內(nèi)存是線程共享的這個命題是不準(zhǔn)確的扑毡,其eden區(qū)域中還是有一部分空間是分配給線程獨享的。
雖然說TLAB是線程獨享的盛险,但是只是在
分配
這個動作上是線程獨享的瞄摊,至于在讀取、垃圾回收等動作上都是線程共享的苦掘。而且在使用上也沒有什么區(qū)別换帜。
雖然每個線程在初始化時都會去堆內(nèi)存中申請一塊TLAB,并不是說這個TLAB區(qū)域的內(nèi)存其他線程就完全無法訪問了鹤啡,其他線程的讀取還是可以的惯驼,只不過無法在這個區(qū)域中分配內(nèi)存而已。并且递瑰,在TLAB分配之后祟牲,并不影響對象的移動和回收,也就是說抖部,雖然對象剛開始可能通過TLAB分配內(nèi)存说贝,存放在Eden區(qū),但是還是會被垃圾回收或者被移到Survivor Space慎颗、Old Gen等乡恕。
有一點需要注意的是,我們說TLAB是在eden區(qū)分配的俯萎,因為eden區(qū)域本身就不太大傲宜,而且TLAB空間的內(nèi)存也非常小,默認情況下僅占有整個Eden空間的1%讯屈。所以,必然存在一些大對象是無法在TLAB直接分配县习。遇到TLAB中無法分配的大對象涮母,對象還是可能在eden區(qū)或者老年代等進行分配的,但是這種分配就需要進行同步控制躁愿,這也是經(jīng)常說的:小的對象比大的對象分配起來更加高效
叛本。
2. 完整的對象分配
通過上面說的可以得出結(jié)論,對象分配的全過程如下圖:
TLAB的空間并不大彤钟,所以大對象還是可能需要在堆內(nèi)存中直接分配来候。對象的內(nèi)存分配步驟就是先嘗試TLAB分配,空間不足之后逸雹,再判斷是否應(yīng)該直接進入老年代营搅,然后再確定是再eden分配還是在老年代分配云挟。
3. TLAB帶來的問題
因為TLAB內(nèi)存區(qū)域并不是很大,所以有可能會經(jīng)常出現(xiàn)TLAB內(nèi)存區(qū)域不夠的情況转质。在《實戰(zhàn)Java虛擬機》中有這樣一個例子:
比如一個線程的TLAB空間有100KB园欣,其中已經(jīng)使用了80KB,當(dāng)需要再分配一個30KB的對象時休蟹,就無法直接在TLAB中分配沸枯,遇到這種情況時,有兩種處理方案:
- 如果一個對象需要的空間大小超過TLAB中剩余的空間大小赂弓,則直接在堆內(nèi)存中對該對象進行內(nèi)存分配绑榴。
- 如果一個對象需要的空間大小超過TLAB中剩余的空間大小,則廢棄當(dāng)前TLAB盈魁,重新申請TLAB空間再次進行內(nèi)存分配翔怎。
以上兩個方案各有利弊,如果采用方案1备埃,那么就可能存在著一種極端情況姓惑,就是TLAB只剩下1KB,就會導(dǎo)致后續(xù)需要分配的大多數(shù)對象都需要在堆內(nèi)存直接分配按脚。
如果采用方案2于毙,也有可能存在頻繁廢棄TLAB,頻繁申請TLAB的情況辅搬,雖然在TLAB上分配內(nèi)存是線程獨享的唯沮,但是TLAB內(nèi)存自己從堆中劃分出來的過程確實可能存在沖突的,所以堪遂,TLAB的分配過程其實也是需要并發(fā)控制的介蛉。而頻繁的TLAB分配就失去了使用TLAB的意義。
為了解決這兩個方案存在的問題溶褪,虛擬機定義了一個
refill_waste
的值币旧,這個值可以翻譯為最大浪費空間。
當(dāng)請求分配的內(nèi)存大于refill_waste
的時候猿妈,會選擇在堆內(nèi)存中分配吹菱。若小于refill_waste
值,則會廢棄當(dāng)前TLAB彭则,重新創(chuàng)建TLAB進行對象內(nèi)存分配鳍刷。
前面的例子中,TLAB總空間100KB俯抖,使用了80KB输瓜,剩余20KB,如果設(shè)置的refill_waste的值為25KB,那么如果新對象的內(nèi)存大于25KB尤揣,則直接堆內(nèi)存分配搔啊,如果小于25KB,則會廢棄掉之前的那個TLAB芹缔,重新分配一個TLAB空間坯癣,給新對象分配內(nèi)存。