Java程序中無時無刻都有對象被創(chuàng)建出來扛禽。在語言層面上,對象創(chuàng)建(克隆侈净、反序列化)僅僅是一個new關(guān)鍵字而已尊勿,而在虛擬機中,對象創(chuàng)建(僅限于普通對象畜侦,不包括數(shù)組和Class對象等)是個怎樣的過程呢元扔?
?? 一、虛擬機遇到一條new指令旋膳,首先將去檢查這個指令參數(shù)是否能在常量池中定位到一個類的符號引用澎语,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過验懊。如果沒有擅羞,那必須先執(zhí)行相應(yīng)的類加載過程。
?? 二义图、類加載檢查通過后减俏,虛擬機就為新生對象分配內(nèi)存。對象所需內(nèi)存的大小在類加載完成后便可完全確定碱工,為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來娃承。
??分配空間的分配方式有兩種:
??1.“指針碰撞”:Java堆中內(nèi)存是絕對規(guī)整的,所有用過的內(nèi)存放一邊怕篷,空閑的內(nèi)存放另一邊历筝,中間放一個指針作為分界點的指示器,那所分配的內(nèi)存僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離廊谓。
??2.“空閑列表”: 如果Java堆中內(nèi)存并不規(guī)整梳猪,已使用的內(nèi)存和空閑的內(nèi)存相互交錯,那就沒有辦法簡單的進行指針碰撞了蒸痹,虛擬機就必須維護一個列表春弥,記錄上哪些內(nèi)存塊可用,在分配的時候电抚,從列表中找出一塊足夠大的空間劃分給對象實例,并更新列表上的記錄竖共。
選擇哪種分配方式由Java堆是否規(guī)整來決定蝙叛。而Java堆是否規(guī)整是由垃圾收集器是否帶有壓縮整理功能決定。如:使用Serial公给、ParNew等帶Compact過程的收集器時借帘,系統(tǒng)采用指針碰撞的分配方式蜘渣。而使用CMS這種基于Mark-Sweep算法的收集器,通常采用的空閑列表肺然。
??對象創(chuàng)建在虛擬機中時非常頻繁的行為蔫缸,即使是僅僅修改一個指針?biāo)赶虻奈恢茫诓l(fā)情況下也并不是線程安全的际起,可能出現(xiàn)正在給對象A分配內(nèi)存拾碌,指針還沒來得及修改,對象B又同時使用了原來的指針來分配內(nèi)存的情況街望,解決這個問題又兩種方案:
??1.對分配內(nèi)存空間的動作進行同步處理--實際上虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性(
原子性是指一個操作是不可中斷的校翔,要么全部執(zhí)行成功要么全部執(zhí)行失敗,有著“同生共死”的感覺灾前。)防症;
??2.把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預(yù)先分配一塊小內(nèi)存哎甲,稱為本地線程分配緩沖區(qū)(Thread Local Allocation Buffer蔫敲,TLAB)。哪個線程要分配內(nèi)存就在哪個線程的TLAB上分配炭玫,只有TLAB用完并分配新的TLAB時奈嘿,才需要同步鎖定。虛擬機是否使用TLAB础嫡,可以通過-XX:+/-UseTLAB參數(shù)來設(shè)定指么。
?? 三、內(nèi)存分配完成后榴鼎,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭)伯诬,如果使用TLAB,這一工作過程也可以提前至TLAB分配時進行巫财。這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用盗似,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的零值。
??四平项、虛擬機要對對象進行必要的設(shè)置赫舒,例如這個對象時哪個類的實例、如何才能找到類的元數(shù)據(jù)信息闽瓢、對象的哈希碼接癌、對象的GC分代年齡等信息。這些都存放在對象的對象頭之中扣讼。
??通過上面的工作缺猛,從虛擬機的視角看,一個新的對象已經(jīng)產(chǎn)生了,但從Java程序的視角來看荔燎,對象創(chuàng)建才剛剛開始耻姥。init方法還沒執(zhí)行,所有的字段都還為零有咨。所以琐簇,一般來說,執(zhí)行new指令之后接著會執(zhí)行init方法座享,把對象按照程序員的意愿進行初始化婉商,這樣一個真正可用的對象才算完全產(chǎn)生。