JAVA | Java對象的內存分配過程是如何保證線程安全的?

專一於Java領域優質技術,歡迎關注

做者 l Hollis 來源 l Hollis(ID:hollischuang)html

JVM內存結構,是很重要的知識,相信每個靜心準備過面試的程序員均可以清楚的把堆、棧、方法區等介紹的比較清楚。程序員

Java對象的內存分配過程是如何保證線程安全的?

上圖,是一張在做者根據《Java虛擬機規範(Java SE 8)》中描述的JVM運行時內存區域結構畫的。面試

不少人都知道Java對象是在堆內存中分配空間的(JIT優化除外),也知道內存分配過程當中是線程安全的,那麼虛擬機究竟是如何保證線程安全的呢?本文就來簡單介紹一下。算法

1 Java對象的內存分配

咱們知道,Java是一門面向對象的語言,咱們在Java中使用的對象都須要被建立出來,在Java中,建立一個對象的方法有不少種,如使用new、使用反射、使用Clone方法等,可是不管如何,對象在建立過程當中,都須要進行內存分配。安全

拿最多見的new關鍵字舉例,當咱們使用new建立對象後代碼開始運行後,虛擬機執行到這條new指令的時候,會先檢查要new的對象對應的類是否已被加載,若是沒有被加載則先進行類加載。併發

在類加載檢查經過以後,就須要給對象進行內存分配了,分配的內存主要用來存放對象的實例變量。學習

在進行內存分配時,須要根據對象中的實例變量狀況等信息肯定須要分配的空間大小,而後從Java堆中劃分出這樣一塊區域(假設沒有JIT優化)。優化

根據JVM使用的垃圾回收器的類型,因其回收算法不一樣,會致使堆中內存分配狀況不一樣。如標記-清楚算法回收後的內存中會有大量不連續的內存碎片,在給新的對象分配的時候,就須要經過"空閒列表"來肯定一塊空閒區域。(這部分不是本文重點,讀者能夠自行學習一下。)線程

不管那種方式,最終都須要肯定出一塊內存區域,用於給新建對象分配內存。咱們知道,對象的內存分配過程當中,主要是對象的引用指向這個內存區域,而後進行初始化操做。htm

那麼問題就來了:

在併發場景中,如何內存分配過程的線程安全性?若是兩個線程前後把對象引用指向了同一個內存區域,怎麼辦。

2 TLAB

通常有兩種解決方案:

  • 一、對分配內存空間的動做作同步處理,採用CAS機制,配合失敗重試的方式保證更新操做的線程安全性。
  • 二、每一個線程在Java堆中預先分配一小塊內存,而後再給對象分配內存的時候,直接在本身這塊"私有"內存中分配,當這部分區域用完以後,再分配新的"私有"內存。

方案1在每次分配時都須要進行同步控制,這種是比較低效的。

方案2是HotSpot虛擬機中採用的,這種方案被稱之爲TLAB分配,即Thread Local Allocation Buffer。這部分Buffer是從堆中劃分出來的,可是是本地線程獨享的。

這裏值得注意的是,咱們說TLAB時線程獨享的,可是隻是在「分配」這個動做上是線程獨佔的,至於在讀取、垃圾回收等動做上都是線程共享的。並且在使用上也沒有什麼區別。

另外,TLAB僅做用於新生代的Eden Space,對象被建立的時候首先放到這個區域,可是新生代分配不了內存的大對象會直接進入老年代。所以在編寫Java程序時,一般多個小的對象比大的對象分配起來更加高效。

因此,雖然對象剛開始可能經過TLAB分配內存,存放在Eden區,可是仍是會被垃圾回收或者被移到Survivor Space、Old Gen等。

不知道你們有沒有想過,咱們使用了TLAB以後,在TLAB上給對象分配內存時線程獨享的了,這就沒有衝突了,可是,TLAB這塊內存自身從堆中劃分出來的過程也可能存在內存安全問題啊。

因此,在對於TLAB的分配過程,仍是須要進行同步控制的。可是這種開銷相比於每次爲單個對象劃份內存時候對進行同步控制的要低的多。

虛擬機是否使用TLAB是能夠選擇的,能夠經過設置-XX:+/-UseTLAB參數來指定。

3 總結

爲了保證Java對象的內存分配的安全性,同時提高效率,每一個線程在Java堆中能夠預先分配一小塊內存,這部份內存稱之爲TLAB(Thread Local Allocation Buffer),這塊內存的分配時線程獨佔的,讀取、使用、回收是線程共享的。

能夠經過設置-XX:+/-UseTLAB參數來指定是否開啓TLAB分配。

參考資料

《深刻理解Java虛擬機》

https://www.cnblogs.com/straybirds/p/8529924.html https://www.zhihu.com/question/56538259

相關文章
相關標籤/搜索