JAVA內(nèi)存區(qū)域 --(2)對象創(chuàng)建
JVM 在遇到一條 new 指令時,是如何為其分配內(nèi)存空間并初始化的呢?
筆者將流程畫成了一個簡單的流程圖:

這里我們先略過第二步的類加載機(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 使用的就是直接指針訪問。