当前位置:网站首页>創建對象的時候堆內存的分配

創建對象的時候堆內存的分配

2022-06-26 10:07:00 zz好好學java

在學習Java的過程中,一般認為new出來的對象都是被分配在堆上的,其實這個結論不完全正確,因為是大部分new出來的對象被分配在堆上,而不是全部。通過對Java對象分配的過程分析,可以知道有另外兩個地方也是可以存放對象的。這兩個地方分別棧 (涉及逃逸分析相關知識)和TLAB(Thread Local Allocation Buffer)。我們首先對這兩者進行介紹,而後對Java對象分配過程進行介紹。

棧上分配

在JVM中,堆是線程共享的,因此堆上的對象對於各個線程都是共享和可見的,只要持有對象的引用,就可以訪問堆中存儲的對象數據。虛擬機的垃圾收集系統可以回收堆中不再使用的對象,但對於垃圾收集器來說,無論篩選可回收對象,還是回收和整理內存都需要耗費時間。

如果確定一個對象的作用域不會逃逸出方法之外,那可以將這個對象分配在棧上,這樣,對象所占用的內存空間就可以隨棧幀出棧而銷毀。在一般應用中,不會逃逸的局部對象所占的比例很大,如果能使用棧上分配,那大量的對象就會隨著方法的結束而自動銷毀了,無須通過垃圾收集器回收,可以减小垃圾收集器的負載。

JVM允許將線程私有的對象打散分配在棧上,而不是分配在堆上。分配在棧上的好處是可以在函數調用結束後自行銷毀,而不需要垃圾回收器的介入,從而提高系統性能。
棧上分配的技術基礎:
一是逃逸分析:逃逸分析的目的是判斷對象的作用域是否有可能逃逸出函數體。關於逃逸分析的問題可以看我另一篇文章:

二是標量替換:允許將對象打散分配在棧上,比如若一個對象擁有兩個字段,會將這兩個字段視作局部變量進行分配。

只能在server模式下才能啟用逃逸分析,參數-XX:DoEscapeAnalysis啟用逃逸分析,參數-XX:+EliminateAllocations開啟標量替換(默認打開)。Java SE 6u23版本之後,HotSpot中默認就開啟了逃逸分析,可以通過選項-XX:+PrintEscapeAnalysis查看逃逸分析的篩選結果。

TLAB

TLAB的全稱是Thread Local Allocation Buffer,即線程本地分配緩存區,這是一個線程專用的內存分配區域。
由於對象一般會分配在堆上,而堆是全局共享的。因此在同一時間,可能會有多個線程在堆上申請空間。因此,每次對象分配都必須要進行同步(虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性),而在競爭激烈的場合分配的效率又會進一步下降。JVM使用TLAB來避免多線程沖突,在給對象分配內存時,每個線程使用自己的TLAB,這樣可以避免線程同步,提高了對象分配的效率。
TLAB本身占用eEden區空間,在開啟TLAB的情况下,虛擬機會為每個Java線程分配一塊TLAB空間。參數-XX:+UseTLAB開啟TLAB,默認是開啟的。TLAB空間的內存非常小,缺省情况下僅占有整個Eden空間的1%,當然可以通過選項-XX:TLABWasteTargetPercent設置TLAB空間所占用Eden空間的百分比大小。
由於TLAB空間一般不會很大,因此大對象無法在TLAB上進行分配,總是會直接分配在堆上。TLAB空間由於比較小,因此很容易裝滿。比如,一個100K的空間,已經使用了80KB,當需要再分配一個30KB的對象時,肯定就無能為力了。這時虛擬機會有兩種選擇,第一,廢弃當前TLAB,這樣就會浪費20KB空間;第二,將這30KB的對象直接分配在堆上,保留當前的TLAB,這樣可以希望將來有小於20KB的對象分配請求可以直接使用這塊空間。實際上虛擬機內部會維護一個叫作refill_waste的值,當請求對象大於refill_waste時,會選擇在堆中分配,若小於該值,則會廢弃當前TLAB,新建TLAB來分配對象。這個閾值可以使用TLABRefillWasteFraction來調整,它錶示TLAB中允許產生這種浪費的比例。默認值為64,即錶示使用約為1/64的TLAB空間作為refill_waste。默認情况下,TLAB和refill_waste都會在運行時不斷調整的,使系統的運行狀態達到最優。如果想要禁用自動調整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,並使用-XX:TLABSize手工指定一個TLAB的大小。
-XX:+PrintTLAB可以跟踪TLAB的使用情况。一般不建議手工修改TLAB相關參數,推薦使用虛擬機默認行為。

對象內存分配的兩種方法

為對象分配空間的任務等同於把一塊確定大小的內存從Java堆中劃分出來。

指針碰撞(Serial、ParNew等帶Compact過程的收集器)
假設Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閑的內存放在另一邊,中間放著一個指針作為分界點的指示器,那所分配內存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。
空閑列錶(CMS這種基於Mark-Sweep算法的收集器)
如果Java堆中的內存並不是規整的,已使用的內存和空閑的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列錶,記錄上哪些內存塊是可用的,在分配的時候從列錶中找到一塊足够大的空間劃分給對象實例,並更新列錶上的記錄,這種分配方式稱為“空閑列錶”(Free List)。
  

總結

總體流程
這裏寫圖片描述

對象分配流程
這裏寫圖片描述
如果開啟棧上分配,JVM會先進行棧上分配,如果沒有開啟棧上分配或則不符合條件的則會進行TLAB分配,如果TLAB分配不成功,再嘗試在eden區分配,如果對象滿足了直接進入老年代的條件,那就直接分配在老年代。

對象在內存的引用方式
這裏寫圖片描述

對象在內存中的結構
這裏寫圖片描述

原网站

版权声明
本文为[zz好好學java]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/177/202206260922192550.html