對象的創(chuàng)建
1.查詢能否在常量池定位到該類的符號引用。
首先,在虛擬機遇到一個new指令時,首先會檢查這個指令的參數是否能在常量池中定位到一個類的符號引用。這個符號引用代表該類是否已經被加載、解析和初始化過。如果沒有就需要先執(zhí)行類加載過程。
2.為對象分配內存空間。
如果java堆中的內存完整,那么會把指向空閑的指針往前移動一段與對象大小相等的距離。這種方式稱為"指針碰撞".

正常狀態(tài)

如果java堆中的內存不完整,已使用的內存與空閑內存相互交錯,那么沒辦法直接進行指針碰撞。虛擬機會維護一個列表,記錄那些內存塊可用,在分配時,更新表上的記錄,這塊表稱為空閑列表。

(拓展:選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
因此,在使用Serial、ParNew等帶Compact過程的收集器時,系統(tǒng)采用的分配算法是指針碰撞,而使用CMS這種基于Mark-Sweep算法的收集器時,通常采用空閑列表。)
3.初始化分配到的內存空間
內存分配完成后,虛擬機要將分配到的內存空間都初始化為0值(這里不包括對象頭)這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。
4.對對象進行必要的設置
接下來,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭(Object Header)之中。根據虛擬機當前的運行狀態(tài)的不同,如是否啟用偏向鎖等,對象頭會有不同的設置方式。
在上面工作都完成之后,從虛擬機的視角來看,一個新的對象已經產生了,但從Java程序的視角來看,對象創(chuàng)建才剛剛開始——<init>方法還沒有執(zhí)行,所有的字段都還為零。所以,一般來說(由字節(jié)碼中是否跟隨invokespecial指令所決定),執(zhí)行new指令之后會接著執(zhí)行<init>方法,把對象按照程序員的意愿進行初始化,這樣一個真正可用的對象才算完全產生出來。
整個流程如圖所示:

給對象分配內存會存在線程不安全的問題
解決 線程不安全 有兩種方案:
1.同步處理分配內存空間的行為
2.虛擬機采用 CAS + 失敗重試的方式 保證更新操作的原子性
同步處理:
把內存分配行為 按照線程 劃分在不同的內存空間進行.
即每個線程在 Java堆中預先分配一小塊內存(本地線程分配緩沖(Thread Local Allocation Buffer ,TLAB)),哪個線程要分配內存,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時才需要同步鎖。
虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數來設定
部分內容取自:
來自《深入理解JVM虛擬機》JVM高級特性與最佳實現。
以及該博主:
https://baijiahao.baidu.com/s?id=1612824465692855405&wfr=spider&for=pc