溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java對(duì)象的內(nèi)存分配過(guò)程是如何保證線(xiàn)程安全的?

發(fā)布時(shí)間:2020-05-22 14:07:36 來(lái)源:億速云 閱讀:297 作者:鴿子 欄目:編程語(yǔ)言

Java作為一種面向?qū)ο蟮?,跨平臺(tái)語(yǔ)言,其對(duì)象、內(nèi)存等一直是比較難的知識(shí)點(diǎn),所以,即使是一個(gè)Java的初學(xué)者,也一定或多或少的對(duì)JVM有一些了解??梢哉f(shuō),關(guān)于JVM的相關(guān)知識(shí),基本是每個(gè)Java開(kāi)發(fā)者必學(xué)的知識(shí)點(diǎn),也是面試的時(shí)候必考的知識(shí)點(diǎn)。

在JVM的內(nèi)存結(jié)構(gòu)中,比較常見(jiàn)的兩個(gè)區(qū)域就是堆內(nèi)存和棧內(nèi)存(如無(wú)特指,本文提到的棧均指的是虛擬機(jī)棧),關(guān)于堆和棧的區(qū)別,很多開(kāi)發(fā)者也是如數(shù)家珍,有很多書(shū)籍,或者網(wǎng)上的文章大概都是這樣介紹的:

1、堆是線(xiàn)程共享的內(nèi)存區(qū)域,棧是線(xiàn)程獨(dú)享的內(nèi)存區(qū)域。

2、堆中主要存放對(duì)象實(shí)例,棧中主要存放各種基本數(shù)據(jù)類(lèi)型、對(duì)象的引用。

但是,作者可以很負(fù)責(zé)任的告訴大家,以上兩個(gè)結(jié)論均不是完全正確的。

本文首先帶大家了解一下為什么我會(huì)說(shuō)“堆是線(xiàn)程共享的內(nèi)存區(qū)域,棧是線(xiàn)程獨(dú)享的內(nèi)存區(qū)域?!边@句話(huà)并不完全正確!?關(guān)于JVM內(nèi)存結(jié)構(gòu)的相關(guān)知識(shí),大家可以閱讀JVM內(nèi)存結(jié)構(gòu) VS Java內(nèi)存模型 VS Java對(duì)象模型、萬(wàn)萬(wàn)沒(méi)想到,JVM內(nèi)存結(jié)構(gòu)的面試題可以問(wèn)的這么難?等文章。

我們知道,Java是一門(mén)面向?qū)ο蟮恼Z(yǔ)言,我們?cè)贘ava中使用的對(duì)象都需要被創(chuàng)建出來(lái),在Java中,創(chuàng)建一個(gè)對(duì)象的方法有很多種,但是無(wú)論如何,對(duì)象在創(chuàng)建過(guò)程中,都需要進(jìn)行內(nèi)存分配。

對(duì)象的內(nèi)存分配過(guò)程中,主要是對(duì)象的引用指向這個(gè)內(nèi)存區(qū)域,然后進(jìn)行初始化操作。

但是,因?yàn)槎咽侨止蚕淼?,因此在同一時(shí)間,可能有多個(gè)線(xiàn)程在堆上申請(qǐng)空間,那么,在并發(fā)場(chǎng)景中,如果兩個(gè)線(xiàn)程先后把對(duì)象引用指向了同一個(gè)內(nèi)存區(qū)域,怎么辦。

Java對(duì)象的內(nèi)存分配過(guò)程是如何保證線(xiàn)程安全的?


為了解決這個(gè)并發(fā)問(wèn)題,對(duì)象的內(nèi)存分配過(guò)程就必須進(jìn)行同步控制。但是我們都知道,無(wú)論是使用哪種同步方案(實(shí)際上虛擬機(jī)使用的可能是CAS),都會(huì)影響內(nèi)存的分配效率。

而Java對(duì)象的分配是Java中的高頻操作,所有,人們想到另外一個(gè)辦法來(lái)提升效率。這里我們重點(diǎn)說(shuō)一個(gè)HotSpot虛擬機(jī)的方案:

每個(gè)線(xiàn)程在Java堆中預(yù)先分配一小塊內(nèi)存,然后再給對(duì)象分配內(nèi)存的時(shí)候,直接在自己這塊”私有”內(nèi)存中分配,當(dāng)這部分區(qū)域用完之后,再分配新的”私有”內(nèi)存。

這種方案被稱(chēng)之為T(mén)LAB分配,即Thread Local Allocation Buffer。這部分Buffer是從堆中劃分出來(lái)的,但是是本地線(xiàn)程獨(dú)享的。

什么是TLAB

TLAB是虛擬機(jī)在堆內(nèi)存的eden劃分出來(lái)的一塊專(zhuān)用空間,是線(xiàn)程專(zhuān)屬的。在虛擬機(jī)的TLAB功能啟動(dòng)的情況下,在線(xiàn)程初始化時(shí),虛擬機(jī)會(huì)為每個(gè)線(xiàn)程分配一塊TLAB空間,只給當(dāng)前線(xiàn)程使用,這樣每個(gè)線(xiàn)程都單獨(dú)擁有一個(gè)空間,如果需要分配內(nèi)存,就在自己的空間上分配,這樣就不存在競(jìng)爭(zhēng)的情況,可以大大提升分配效率。

注意到上面的描述中"線(xiàn)程專(zhuān)屬"、"只給當(dāng)前線(xiàn)程使用"、"每個(gè)線(xiàn)程單獨(dú)擁有"的描述了嗎?

所以說(shuō),因?yàn)橛辛薚LAB技術(shù),堆內(nèi)存并不是完完全全的線(xiàn)程共享,其eden區(qū)域中還是有一部分空間是分配給線(xiàn)程獨(dú)享的。

這里值得注意的是,我們說(shuō)TLAB是線(xiàn)程獨(dú)享的,但是只是在“分配”這個(gè)動(dòng)作上是線(xiàn)程獨(dú)享的,至于在讀取、垃圾回收等動(dòng)作上都是線(xiàn)程共享的。而且在使用上也沒(méi)有什么區(qū)別

Java對(duì)象的內(nèi)存分配過(guò)程是如何保證線(xiàn)程安全的?


也就是說(shuō),雖然每個(gè)線(xiàn)程在初始化時(shí)都會(huì)去堆內(nèi)存中申請(qǐng)一塊TLAB,并不是說(shuō)這個(gè)TLAB區(qū)域的內(nèi)存其他線(xiàn)程就完全無(wú)法訪問(wèn)了,其他線(xiàn)程的讀取還是可以的,只不過(guò)無(wú)法在這個(gè)區(qū)域中分配內(nèi)存而已。

并且,在TLAB分配之后,并不影響對(duì)象的移動(dòng)和回收,也就是說(shuō),雖然對(duì)象剛開(kāi)始可能通過(guò)TLAB分配內(nèi)存,存放在Eden區(qū),但是還是會(huì)被垃圾回收或者被移到Survivor Space、Old Gen等。

Java對(duì)象的內(nèi)存分配過(guò)程是如何保證線(xiàn)程安全的?


還有一點(diǎn)需要注意的是,我們說(shuō)TLAB是在eden區(qū)分配的,因?yàn)閑den區(qū)域本身就不太大,而且TLAB空間的內(nèi)存也非常小,默認(rèn)情況下僅占有整個(gè)Eden空間的1%。所以,必然存在一些大對(duì)象是無(wú)法在TLAB直接分配。

遇到TLAB中無(wú)法分配的大對(duì)象,對(duì)象還是可能在eden區(qū)或者老年代等進(jìn)行分配的,但是這種分配就需要進(jìn)行同步控制,這也是為什么我們經(jīng)常說(shuō):小的對(duì)象比大的對(duì)象分配起來(lái)更加高效。

TLAB帶來(lái)的問(wèn)題

雖然在一定程度上,TLAB大大的提升了對(duì)象的分配速度,但是TLAB并不是就沒(méi)有任何問(wèn)題的。

前面我們說(shuō)過(guò),因?yàn)門(mén)LAB內(nèi)存區(qū)域并不是很大,所以,有可能會(huì)經(jīng)常出現(xiàn)不夠的情況。在《實(shí)戰(zhàn)Java虛擬機(jī)》中有這樣一個(gè)例子:

比如一個(gè)線(xiàn)程的TLAB空間有100KB,其中已經(jīng)使用了80KB,當(dāng)需要再分配一個(gè)30KB的對(duì)象時(shí),就無(wú)法直接在TLAB中分配,遇到這種情況時(shí),有兩種處理方案:

1、如果一個(gè)對(duì)象需要的空間大小超過(guò)TLAB中剩余的空間大小,則直接在堆內(nèi)存中對(duì)該對(duì)象進(jìn)行內(nèi)存分配。

2、如果一個(gè)對(duì)象需要的空間大小超過(guò)TLAB中剩余的空間大小,則廢棄當(dāng)前TLAB,重新申請(qǐng)TLAB空間再次進(jìn)行內(nèi)存分配。

以上兩個(gè)方案各有利弊,如果采用方案1,那么就可能存在著一種極端情況,就是TLAB只剩下1KB,就會(huì)導(dǎo)致后續(xù)需要分配的大多數(shù)對(duì)象都需要在堆內(nèi)存直接分配。

如果采用方案2,也有可能存在頻繁廢棄TLAB,頻繁申請(qǐng)TLAB的情況,而我們知道,雖然在TLAB上分配內(nèi)存是線(xiàn)程獨(dú)享的,但是TLAB內(nèi)存自己從堆中劃分出來(lái)的過(guò)程確實(shí)可能存在沖突的,所以,TLAB的分配過(guò)程其實(shí)也是需要并發(fā)控制的。而頻繁的TLAB分配就失去了使用TLAB的意義。

為了解決這兩個(gè)方案存在的問(wèn)題,虛擬機(jī)定義了一個(gè)refill_waste的值,這個(gè)值可以翻譯為“最大浪費(fèi)空間”。

當(dāng)請(qǐng)求分配的內(nèi)存大于refill_waste的時(shí)候,會(huì)選擇在堆內(nèi)存中分配。若小于refill_waste值,則會(huì)廢棄當(dāng)前TLAB,重新創(chuàng)建TLAB進(jìn)行對(duì)象內(nèi)存分配。

前面的例子中,TLAB總空間100KB,使用了80KB,剩余20KB,如果設(shè)置的refill_waste的值為25KB,那么如果新對(duì)象的內(nèi)存大于25KB,則直接堆內(nèi)存分配,如果小于25KB,則會(huì)廢棄掉之前的那個(gè)TLAB,重新分配一個(gè)TLAB空間,給新對(duì)象分配內(nèi)存。

TLAB使用的相關(guān)參數(shù)

TLAB功能是可以選擇開(kāi)啟或者關(guān)閉的,可以通過(guò)設(shè)置-XX:+/-UseTLAB參數(shù)來(lái)指定是否開(kāi)啟TLAB分配。

TLAB默認(rèn)是eden區(qū)的1%,可以通過(guò)選項(xiàng)-XX:TLABWasteTargetPercent設(shè)置TLAB空間所占用Eden空間的百分比大小。

默認(rèn)情況下,TLAB的空間會(huì)在運(yùn)行時(shí)不斷調(diào)整,使系統(tǒng)達(dá)到最佳的運(yùn)行狀態(tài)。如果需要禁用自動(dòng)調(diào)整TLAB的大小,可以使用-XX:-ResizeTLAB來(lái)禁用,并且使用-XX:TLABSize來(lái)手工指定TLAB的大小。

TLAB的refill_waste也是可以調(diào)整的,默認(rèn)值為64,即表示使用約為1/64空間大小作為refill_waste,使用參數(shù):-XX:TLABRefillWasteFraction來(lái)調(diào)整。

如果想要觀察TLAB的使用情況,可以使用參數(shù)-XX+PringTLAB 進(jìn)行跟蹤。

總結(jié)

為了保證對(duì)象的內(nèi)存分配過(guò)程中的線(xiàn)程安全性,HotSpot虛擬機(jī)提供了一種叫做TLAB(Thread Local Allocation Buffer)的技術(shù)。

在線(xiàn)程初始化時(shí),虛擬機(jī)會(huì)為每個(gè)線(xiàn)程分配一塊TLAB空間,只給當(dāng)前線(xiàn)程使用,當(dāng)需要分配內(nèi)存時(shí),就在自己的空間上分配,這樣就不存在競(jìng)爭(zhēng)的情況,可以大大提升分配效率。

所以,“堆是線(xiàn)程共享的內(nèi)存區(qū)域”這句話(huà)并不完全正確,因?yàn)門(mén)LAB是堆內(nèi)存的一部分,他在讀取上確實(shí)是線(xiàn)程共享的,但是在內(nèi)存分配上,是線(xiàn)程獨(dú)享的。

TLAB的空間其實(shí)并不大,所以大對(duì)象還是可能需要在堆內(nèi)存中直接分配。那么,對(duì)象的內(nèi)存分配步驟就是先嘗試TLAB分配,空間不足之后,再判斷是否應(yīng)該直接進(jìn)入老年代,然后再確定是再eden分配還是在老年代分配。

Java對(duì)象的內(nèi)存分配過(guò)程是如何保證線(xiàn)程安全的?



TLAB只是HotSpot虛擬機(jī)的一個(gè)優(yōu)化方案,Java虛擬機(jī)規(guī)范中也沒(méi)有關(guān)于TLAB的任何規(guī)定。所以,不代表所有的虛擬機(jī)都有這個(gè)特性。

本文的概述都是基于HotSpot虛擬機(jī)的,因?yàn)镠otSpot虛擬機(jī)是目前最流行的虛擬機(jī)了,大多數(shù)默認(rèn)情況下,我們討論的時(shí)候也都是基于HotSpot的。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI