偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

聊聊Java對(duì)象棧上分配

開發(fā) 前端
假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。

通過對(duì)象的分配過程分析,除了堆以外,還有兩個(gè)地方可以存放對(duì)象:

棧和TLAB(Thread Local Allocation Buffer)。

Java對(duì)象分配流程圖:

如果開啟棧上分配,JVM會(huì)先進(jìn)行棧上分配,如果沒有開啟棧上分配或則不符合條件的則會(huì)進(jìn)行TLAB分配,如果TLAB分配不成功,再嘗試在eden區(qū)分配,如果對(duì)象滿足了直接進(jìn)入老年代的條件,那就直接分配在老年代。

棧上分配

在JVM中,堆是線程共享的,因此堆上的對(duì)象對(duì)于各個(gè)線程都是共享和可見的,只要持有對(duì)象的引用,就可以訪問堆中存儲(chǔ)的對(duì)象數(shù)據(jù)。虛擬機(jī)的垃圾收集系統(tǒng)可以回收堆中不再使用的對(duì)象,但對(duì)于垃圾收集器來說,無論篩選可回收對(duì)象,還是回收和整理內(nèi)存都需要耗費(fèi)時(shí)間。

如果確定一個(gè)對(duì)象的作用域不會(huì)逃逸出方法之外,那可以將這個(gè)對(duì)象分配在棧上,這樣,對(duì)象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀。在一般應(yīng)用中,不會(huì)逃逸的局部對(duì)象所占的比例很大,如果能使用棧上分配,那大量的對(duì)象就會(huì)隨著方法的結(jié)束而自動(dòng)銷毀了,無須通過垃圾收集器回收,可以減小垃圾收集器的負(fù)載。

JVM允許將線程私有的對(duì)象打散分配在棧上,而不是分配在堆上。分配在棧上的好處是可以在函數(shù)調(diào)用結(jié)束后自行銷毀,而不需要垃圾回收器的介入,從而提高系統(tǒng)性能。

棧上分配的技術(shù)基礎(chǔ):

一是逃逸分析:逃逸分析的目的是判斷對(duì)象的作用域是否有可能逃逸出函數(shù)體。關(guān)于逃逸分析的問題可以看我另一篇文章:

二是標(biāo)量替換:允許將對(duì)象打散分配在棧上,比如若一個(gè)對(duì)象擁有兩個(gè)字段,會(huì)將這兩個(gè)字段視作局部變量進(jìn)行分配。

只能在server模式下才能啟用逃逸分析,參數(shù)-XX:DoEscapeAnalysis啟用逃逸分析,參數(shù)-XX:+EliminateAllocations開啟標(biāo)量替換(默認(rèn)打開)。Java SE 6u23版本之后,HotSpot中默認(rèn)就開啟了逃逸分析,可以通過選項(xiàng)-XX:+PrintEscapeAnalysis查看逃逸分析的篩選結(jié)果。

TLAB(Thread Local Allocation Buffer)

`TLAB的全稱是Thread Local Allocation Buffer,即線程本地分配緩存區(qū),這是一個(gè)線程專用的內(nèi)存分配區(qū)域。

由于對(duì)象一般會(huì)分配在堆上,而堆是全局共享的。因此在同一時(shí)間,可能會(huì)有多個(gè)線程在堆上申請(qǐng)空間。因此,每次對(duì)象分配都必須要進(jìn)行同步(虛擬機(jī)采用CAS配上失敗重試的方式保證更新操作的原子性),而在競(jìng)爭(zhēng)激烈的場(chǎng)合分配的效率又會(huì)進(jìn)一步下降。JVM使用TLAB來避免多線程沖突,在給對(duì)象分配內(nèi)存時(shí),每個(gè)線程使用自己的TLAB,這樣可以避免線程同步,提高了對(duì)象分配的效率。

TLAB本身占用eEden區(qū)空間,在開啟TLAB的情況下,虛擬機(jī)會(huì)為每個(gè)Java線程分配一塊TLAB空間。參數(shù)-XX:+UseTLAB開啟TLAB,默認(rèn)是開啟的。TLAB空間的內(nèi)存非常小,缺省情況下僅占有整個(gè)Eden空間的1%,當(dāng)然可以通過選項(xiàng)-XX:TLABWasteTargetPercent設(shè)置TLAB空間所占用Eden空間的百分比大小。

由于TLAB空間一般不會(huì)很大,因此大對(duì)象無法在TLAB上進(jìn)行分配,總是會(huì)直接分配在堆上。TLAB空間由于比較小,因此很容易裝滿。比如,一個(gè)100K的空間,已經(jīng)使用了80KB,當(dāng)需要再分配一個(gè)30KB的對(duì)象時(shí),肯定就無能為力了。這時(shí)虛擬機(jī)會(huì)有兩種選擇,第一,廢棄當(dāng)前TLAB,這樣就會(huì)浪費(fèi)20KB空間;第二,將這30KB的對(duì)象直接分配在堆上,保留當(dāng)前的TLAB,這樣可以希望將來有小于20KB的對(duì)象分配請(qǐng)求可以直接使用這塊空間。實(shí)際上虛擬機(jī)內(nèi)部會(huì)維護(hù)一個(gè)叫作refill_waste的值,當(dāng)請(qǐng)求對(duì)象大于refill_waste時(shí),會(huì)選擇在堆中分配,若小于該值,則會(huì)廢棄當(dāng)前TLAB,新建TLAB來分配對(duì)象。

這個(gè)閾值可以使用TLABRefillWasteFraction來調(diào)整,它表示TLAB中允許產(chǎn)生這種浪費(fèi)的比例。默認(rèn)值為64,即表示使用約為1/64的TLAB空間作為refill_waste。默認(rèn)情況下,TLAB和refill_waste都會(huì)在運(yùn)行時(shí)不斷調(diào)整的,使系統(tǒng)的運(yùn)行狀態(tài)達(dá)到最優(yōu)。如果想要禁用自動(dòng)調(diào)整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一個(gè)TLAB的大小,-XX:+PrintTLAB可以跟蹤TLAB的使用情況。一般不建議手工修改TLAB相關(guān)參數(shù),推薦使用虛擬機(jī)默認(rèn)行為。`

所謂TLAB其實(shí)就是這樣的一個(gè)東西:(簡(jiǎn)化偽代碼)

struct ThreadLocalAllocBuffer {
HeapWord* _start;

HeapWord* _top;

HeapWord* _end;

};

每個(gè)線程會(huì)從Eden分配一大塊空間,例如說100KB,作為自己的TLAB。這個(gè)start是TLAB的起始地址,end是TLAB的末尾,然后top是當(dāng)前的分配指針。顯然start <= top < end。

在Eden分配空間時(shí),用的是bump-the-pointer方式來分配,但由于Eden是所有Java線程所共享的,在bump pointer的時(shí)候必須加鎖(或者CAS)才可以保證安全;而當(dāng)每個(gè)線程從Eden分配到一塊空間當(dāng)作TLAB來用之后,在TLAB里分配小塊空間同樣是bump-the-pointer(上面示意的top指針)則不需要加鎖。 當(dāng)一個(gè)Java線程在自己的TLAB中分配到盡頭之后,再要分配就會(huì)出發(fā)一次“TLAB refill”,也就是說之前自己的TLAB就“不管了”(所有權(quán)交回給共享的Eden),然后重新從Eden里分配一塊空間作為新的TLAB。所謂“不管了”并不是說就讓舊TLAB里的對(duì)象直接死掉,而是把那塊空間的控制權(quán)歸還給普通的Eden,里面的對(duì)象該怎樣還是怎樣。

通常情況下,在TLAB中分配多次才會(huì)填滿TLAB、觸發(fā)TLAB refill,這樣使用TLAB分配就比直接從共享部分的Eden分配要均攤(amortized)了同步開銷,于是提高了性能。其實(shí)很多關(guān)注多線程性能的malloc庫實(shí)現(xiàn)也會(huì)使用類似的做法,例如TCMalloc。

到觸發(fā)GC的時(shí)候,無論是minor GC還是full GC,要收集Eden的時(shí)候里面的空間無論是屬于某個(gè)線程的TLAB還是不屬于任何TLAB都一視同仁,把Eden當(dāng)作一個(gè)整體來收集里面的對(duì)象——把活的對(duì)象拷貝到survivor space(或者直接晉升到Old Gen)。在GC結(jié)束之后,每個(gè)Java線程又會(huì)重新從Eden分配自己的TLAB。周而復(fù)始。

想像這樣的代碼:

public class Test {
public static Test sharedStatic;
public Test sharedInstanceField;
public static void foo() {
Test localVar = new Test(); // 1
if (sharedStatic == null) {
sharedStatic = localVar; // 2
} else {
sharedStatic.sharedInstanceField = localVar; // 3
}
}
}

(這個(gè)例子純粹為了示意“獨(dú)占”與“共享”的概念,請(qǐng)不要吐槽線程安全問題 ),我們?cè)?(1) 創(chuàng)建了一個(gè)新的Test實(shí)例。如題主所說,在啟動(dòng)UseTLAB(默認(rèn)開啟)的時(shí)候,這個(gè)Test實(shí)例會(huì)被分配在當(dāng)前執(zhí)行Test.foo()的線程的TLAB里。TLAB在執(zhí)行分配動(dòng)作的時(shí)候要更新top指針,而更新這個(gè)指針不需要加任何鎖。

對(duì)象內(nèi)存分配的兩種方法

為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來。

指針碰撞(Serial、ParNew等帶Compact過程的收集器)

假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那所分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。

空閑列表(CMS這種基于Mark-Sweep算法的收集器)

如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒有辦法簡(jiǎn)單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄,這種分配方式稱為“空閑列表”(Free List)。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2021-03-22 11:51:22

Java內(nèi)存棧上

2021-09-28 07:12:09

函數(shù)內(nèi)存

2022-10-08 08:01:07

JVMTLABPLAB

2019-07-23 15:04:54

JavaScript調(diào)用棧事件循環(huán)

2021-12-16 06:52:33

C語言內(nèi)存分配

2022-11-30 08:19:15

內(nèi)存分配Go逃逸分析

2022-02-11 09:31:23

IPV4IP地址IANA

2021-12-02 09:13:56

序列壓入

2015-11-16 11:22:05

Java對(duì)象內(nèi)存分配

2009-06-03 15:52:34

堆內(nèi)存棧內(nèi)存Java內(nèi)存分配

2020-11-26 18:18:21

微服務(wù)業(yè)務(wù)規(guī)模技術(shù)

2023-12-28 09:55:08

隊(duì)列數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)

2018-03-27 10:06:26

對(duì)象存儲(chǔ)演進(jìn)

2024-01-31 09:50:10

JVM逃逸分析HotSpot

2023-03-26 00:43:42

JVM對(duì)象測(cè)試

2018-02-08 14:57:22

對(duì)象內(nèi)存分配

2025-06-05 08:05:00

vectorC++對(duì)象存儲(chǔ)

2022-03-16 08:39:19

StackHeap內(nèi)存

2022-09-07 07:27:36

函數(shù)元素

2021-03-02 06:00:05

Docker.NET 5 Dockerfile
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)