檢查
- 首先去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用它掂,并且檢查這個符號引用代表的類是否已經(jīng)被加載、解析和初始化過俊啼。
類加載
- 如果沒有策幼,那必須先執(zhí)行相應(yīng)的類加載過程邑时。
堆分配內(nèi)存
- 在類加載檢查通過后,接下來虛擬機(jī)將為新生對象分配內(nèi)存特姐。對象所需內(nèi)存的大小在類加載完成后便是可以完全確定的了晶丘。為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來。
指針碰撞
- 假設(shè)Java堆中的內(nèi)存是絕對規(guī)整的唐含,所有用過的內(nèi)存放一邊浅浮,空閑的內(nèi)存放一邊,中間放一個指針作為分界點(diǎn)的指示器捷枯,那所分配的內(nèi)存就是把指針向空閑空間那邊移動一段跟對象大小相等的距離滚秩,這種分配方式就是
指針碰撞
空閑列表
- 如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑內(nèi)存相互交錯淮捆,那就沒有辦法簡單的進(jìn)行指針碰撞來分配內(nèi)存了郁油。虛擬機(jī)必須維護(hù)一個列表來記錄哪些內(nèi)存塊是可用的,在分配的時候從列表中找一塊足夠大的空間劃分給對象實(shí)例争剿,并更新列表上的記錄已艰,這種分配方式稱為
空閑列表
指針碰撞 VS 空閑列表
- 選擇那種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定蚕苇。
- 因此哩掺,在使用Serial、ParNew等帶Compact過程的收集器時涩笤,系統(tǒng)采用的分配算法是指針碰撞嚼吞。而使用CMS這種基于Mark-Sweep算法的收集器時盒件,系統(tǒng)通常采用的分配算法是空閑列表
并發(fā)問題
- 對象創(chuàng)建在虛擬機(jī)中是非常頻繁的,可能出現(xiàn)并發(fā)的情況,解決并發(fā)問題有兩種方案
- 一種是對分配內(nèi)存空間的動作做同步處理舱禽,實(shí)際上虛擬機(jī)采用
CSA配上失敗重試
的方式保證更新操作的原子性 - 另一種是把內(nèi)存分配的動作按照線程劃分在不同的空間之中進(jìn)行炒刁,即每個線程在Java堆中預(yù)先分配一小塊內(nèi)存,稱為
本地線程分配緩沖
(TLAB
(Thread Local Allocation Buffer))誊稚。那個線程要分配內(nèi)存翔始,就在那個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時里伯,才需要同步鎖定城瞎。虛擬機(jī)是否使用TLAB可以使用-XX:+/-UseTLAB
參數(shù)來設(shè)定。
- 一種是對分配內(nèi)存空間的動作做同步處理舱禽,實(shí)際上虛擬機(jī)采用
初始化零值
- 內(nèi)存分配完成后疾瓮,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭)脖镀,如果使用TLAB,這一工作過程也可以提前至TLAB分配時進(jìn)行狼电。這一步的操作保證了對象的實(shí)例字段在Java代碼中可以不賦初始值就直接使用蜒灰,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的零值。
對對象進(jìn)行必要的設(shè)置
- 接下來肩碟,虛擬機(jī)要對對象做必要的設(shè)置强窖,例如這個對象是哪個類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息削祈、對象的哈希碼毕骡、對象的GC分代年齡等信息。這些信息存放在對象的對象頭岩瘦。根據(jù)虛擬機(jī)運(yùn)行狀態(tài)的不同未巫,如是否啟用偏向鎖等,對象頭會有不同的設(shè)置方式启昧。
初始化
- 在上面的工作都完成后叙凡,從虛擬機(jī)的角度來講,對象已經(jīng)創(chuàng)建完成密末。但是從Java程序的角度來講握爷,對象創(chuàng)建才剛剛開始。init方法還沒有執(zhí)行严里,所有的字段都還為零新啼。所以,一般來說(由字節(jié)碼中是否跟隨invokespecial指令所決定)刹碾,執(zhí)行new指令后會接著執(zhí)行init方法,把對象按照程序的意愿進(jìn)行初始化,這樣一個可用的對象才算是創(chuàng)建成功物舒。