- 對象的創(chuàng)建
- 類加載
- 分配內(nèi)存
- 方式
- 方案
- 對象初始化
- 對象頭(Object Header)設(shè)置
- 創(chuàng)建完成
- 對象的內(nèi)存分布
- 對象頭
- 實例數(shù)據(jù)
- 對齊填充
- 對象的訪問
- 句柄
- 直接指針訪問
對象的創(chuàng)建
類加載
虛擬機(jī)接受到new指令時,會查看該對象的類是否已經(jīng)加載,如果沒有則先執(zhí)行類加載步驟。類加載:加載、驗證、準(zhǔn)備、解析、初始化、使用和卸載

分配內(nèi)存
方式
- 指針碰撞(Bump the Pointer),假設(shè)Java堆中內(nèi)存是絕對規(guī)整的,分配過的內(nèi)存放在一邊,未分配的內(nèi)存放在另一邊,中間有個指針作為臨界點的指示器,分配內(nèi)存就是把指針向空閑空間那邊移動一段與對象大小相等的距離。
- 空閑列表(Free List),當(dāng)Java堆中的內(nèi)存不規(guī)整時,分配過的內(nèi)存和未分配的內(nèi)存交錯,則需要維護(hù)一個列表來記錄未分配的空間,虛擬機(jī)就必須維護(hù)一個列表,記錄那些內(nèi)存塊是可用的,在分配內(nèi)存的時候從列表中找出一塊能夠容納新對象的空間劃分給對象實例,并更新該列表上的記錄即可。
方案
- 同步分配空間方法,對分配內(nèi)存空間的動作進(jìn)行同步處理——實際上虛擬機(jī)采用CAS配上失敗重試的方式保證更新操作的原子性
- 本地線程分配緩沖(TLAB),把內(nèi)存分配的動作按照線程劃分在不同的空間之中進(jìn)行,即每個線程在Java堆中預(yù)先分配一小塊內(nèi)存,哪個線程要分配內(nèi)存,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時,才需要同步鎖定。
對象初始化
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭)—保證了在使用實例對象時可以不賦初始值(final除外)
對象頭(Object Header)設(shè)置
需要在對象頭中設(shè)置:對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡等信息。根據(jù)虛擬機(jī)當(dāng)前的運(yùn)行狀態(tài)的不同,比如是否啟用偏向鎖等,對象頭硅油不同的設(shè)置方式。
創(chuàng)建完成
經(jīng)過以上步驟,對于虛擬機(jī)來說:一個新的對象已經(jīng)產(chǎn)生了。對于程序員來說 :對象創(chuàng)建才剛剛開始,執(zhí)行new指令后會接著執(zhí)行<init>方法,再按程序員的意思進(jìn)行初始化,這樣一個真正可用的對象才算完全產(chǎn)生出來。
對象的內(nèi)存分布
對象頭
基本信息,對象頭的第一部分用來存儲對象自身的運(yùn)行數(shù)據(jù):如哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有鎖、偏向線程ID、偏向時間戳。這部分?jǐn)?shù)據(jù)的長度在32和64位的虛擬機(jī)中分別為32bit和64bit,官方稱為“Mark World”。但是對象存儲的數(shù)據(jù)比較多,一般都會大于32,64位Bitmap結(jié)構(gòu)所能記錄的長度,考慮到虛擬機(jī)的空間效率,Mark Word被設(shè)計成一個非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存儲盡量多的信息,它會根據(jù)對象的狀態(tài)復(fù)用自己的存儲空間 。
類型指針,對象頭的另一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個指針來確定這個對象是哪個類的實例(并非所有的虛擬機(jī)實現(xiàn)都必須在對象中記錄這個指針);如果對象是一個Java數(shù)組,那在對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù),因為虛擬機(jī)可以通過普通Java對象的元數(shù)據(jù)信息確定Java對象的大小,但是從數(shù)組的元數(shù)據(jù)中卻無法確定數(shù)組的大小。
實例數(shù)據(jù)
實例數(shù)據(jù)是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容,無論是父類繼承下來的還是子類定義的都需要記錄下來。實例數(shù)據(jù)存儲順序:與虛擬機(jī)分配策略參數(shù)和字段在Java源碼中定義的順序有關(guān)。默認(rèn)分配策略:longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),從分配策略中可以看出,相同寬度的字段總是被分配到一起。
對齊填充
該部分不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。由于HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,換句話說,就是對象的大小必須是8字節(jié)的整數(shù)倍。而對象頭部分正好是8字節(jié)的倍數(shù)(1倍或者2倍),因此,當(dāng)對象實例數(shù)據(jù)部分沒有對齊時,就需要通過對齊填充來補(bǔ)全。
對象的訪問
句柄
如果使用句柄訪問的話,那么Java堆中將會劃分出一塊內(nèi)存來作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息。使用句柄來訪問的最大好處就是reference中存儲的是穩(wěn)定的句柄地址,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數(shù)據(jù)指針,而reference本身不需要修改。

直接指針訪問
指針訪問 那么Java堆對象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息,而reference中存儲的直接就是對象地址。使用直接指針訪問方式的最大好處就是速度更快,它節(jié)省了一次指針定位的時間開銷,由于對象的訪問在Java中非常頻繁,因此這類開銷積少成多后也是一項非常可觀的執(zhí)行成本。HotSpot使用的是直接指針這種方式訪問對象的。
