JAVA內(nèi)存區(qū)域 --(2)對象創(chuàng)建

JAVA內(nèi)存區(qū)域 --(2)對象創(chuàng)建

JVM 在遇到一條 new 指令時,是如何為其分配內(nèi)存空間并初始化的呢?
筆者將流程畫成了一個簡單的流程圖:

創(chuàng)建對象的流程.png

這里我們先略過第二步的類加載機(jī)制,主要講述后面 4 個步驟。

  • 下文馬上會拿一節(jié)介紹 JVM 是如何為對象分配內(nèi)存的。

  • 虛擬機(jī)講分配到的空間初始化為零值
    保證了對象的實(shí)例字段不賦值的時候訪問到的是自卸字段所對應(yīng)的零值(對象是null)。

  • 設(shè)置對象頭
    JVM 對對象進(jìn)行必要的設(shè)置,對象是哪個類的實(shí)例,如何才能找到對象的元數(shù)據(jù)信息,對象的 hashCode,對象的 GC 分代年齡等信息,都會存放到對象頭中。具體對象頭的內(nèi)容在下文中會介紹

  • 執(zhí)行 <init> 方法進(jìn)行初始化步驟。

分配內(nèi)存

確認(rèn)內(nèi)存位置

分配對象的過程中,需要為對象劃分足夠大的內(nèi)存空間,而如何從 Java 堆中找到合適大小的空間,通常用以下兩種方法:

  • 指針碰撞(Bump the Pointer)
    保證 Java 堆中內(nèi)存時絕對規(guī)整的,所有用過內(nèi)存和空閑的內(nèi)存各占一邊,中間放著一個指針作為分界點(diǎn)的指示器。而分配內(nèi)存的過程就是將指示指針向空閑空間移動對象大小的距離。

  • 空閑列表(Free List)
    JVM 維護(hù)一個列表,記錄內(nèi)存塊的使用情況,分配過程則是在列表中找到一塊足夠大的空間分配給對象實(shí)例,并更新表上記錄。

而以上兩種方法由虛擬機(jī)的 Java 堆是否規(guī)整決定,也就是由 GC 算法是否具備壓縮整理的能力決定。

確保線程安全

多個線程在創(chuàng)建對象時,為了保證分配內(nèi)存空間的動作是同步處理的:

  • CAS 配上失敗重試的方式保證更新操作的原子性

  • 本地線程分配緩沖(Thread Local Allocation Buffer)
    每個線程在 Java 堆中預(yù)先分配一小塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer)。每個線程在各自 TLAB 上分配內(nèi)存。只有在 TLAB 用完之后并分配新的 TLAB 時,才需要同步。

對象的內(nèi)存分布

在 HotSpot 虛擬機(jī)中,對象的內(nèi)存儲存布局可以分為3個區(qū)域: 對象頭(Header)、 實(shí)例數(shù)據(jù)(Instance Data)、 對齊填充(Padding)。

對象頭(Header)

對象頭包含兩部分的信息:

  • 存儲自身的運(yùn)行時數(shù)據(jù)
    如 HashCode、GC 分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程 ID、偏向時間戳等
    這部分?jǐn)?shù)據(jù)在 32 位和 64 位虛擬機(jī)中分別占有 32bit 和 64bit ,官方稱為“Mark Word”。

  • 存儲類型指針
    即對象指向它的類元數(shù)據(jù)的指針,JVM 通過這個指針來確認(rèn)這個對象是哪個類的實(shí)例。

如果對象是一個 Java 數(shù)組,那在對象頭中還需要一塊用于記錄數(shù)組長度的數(shù)據(jù)。

實(shí)例數(shù)據(jù)(Instance Data)

實(shí)例數(shù)據(jù)部分是對象真正有效的信息,也就是在程序中定義的各種類型的字段內(nèi)容。
這個存儲順序還會受到虛擬機(jī)的分配策略參數(shù)和字段在 Java 源碼中定義順序影響。HotSpot 默認(rèn)的分配策略為 longs / doubles、ints、shouts / chars、bytes / booleans、oops(Ordinary Object Pointers),即把相同大小的字段分配到一起。

對齊填充(Padding)

對齊填充并不是必須存在的,也沒有特別含義,僅僅起到占位符的作用。因?yàn)?HotSpot 的自動內(nèi)存管理系統(tǒng)要求對象的起址位置必須是8字節(jié)的整數(shù)倍。

對象的訪問定位

我們通過棧上的 reference 數(shù)據(jù)來操作堆上的具體對象。目前通過主流的方式 句柄直接指針 去定位、訪問堆中對象的具體位置。

句柄:Java 堆中將劃出一塊內(nèi)存作為句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象的實(shí)例數(shù)據(jù)地址和類型數(shù)據(jù)的地址。

使用句柄訪問

直接指針訪問:在 Java 堆中對象放置了訪問類型數(shù)據(jù)的相關(guān)地址,而 reference 直接指向?qū)ο髮?shí)例數(shù)據(jù)。

直接訪問
  • 使用句柄的優(yōu)勢:
    在對象被移動時(GC 中移動時十分普遍的行為),只會改變句柄的實(shí)例數(shù)據(jù)指針,而不會修改 reference 本身。
  • 直接訪問的優(yōu)勢:
    節(jié)省了一次指針定位的時間開銷,由于對象訪問在 Java(或者說所有面向?qū)ο蟮恼Z言)中是非常頻繁的。HotSpot 使用的就是直接指針訪問。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容