???????????????? 創(chuàng)建對象的過程
①類加載檢查: JVM將類加載過程分為五個(gè)步驟:
1) 裝載:查找并加載類的二進(jìn)制數(shù)據(jù) 况增;
2)? 驗(yàn)證:確保被加載類的正確性众羡;
3)準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存挫剑,并將其初始化為默認(rèn)值;
4)解析:虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程畸写;
符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標(biāo)贴铜,符號可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可叹括。
直接引用(Direct References):直接引用可以是直接指向目標(biāo)的指針算墨、相對偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。如果有了直接引用汁雷,那么引用的目標(biāo)一定是已經(jīng)存在于內(nèi)存中净嘀。
5) 初始化:為類的靜態(tài)變量賦予正確的初始值。
比如private static int a = 10侠讯,它的執(zhí)行過程是這樣的挖藏,首先字節(jié)碼文件被加載到內(nèi)存后,先進(jìn)行驗(yàn)證這一步驟厢漩,驗(yàn)證通過后準(zhǔn)備階段膜眠,給a分配內(nèi)存,因?yàn)樽兞縜是static的溜嗜,所以此時(shí)a等于int類型的默認(rèn)初始值0宵膨,即a=0,然后解析,到初始化這一步驟時(shí)炸宵,才把a(bǔ)的真正的值10賦給a,此時(shí)a=10柄驻。靜態(tài)變量是在類裝載時(shí)初始化的,因此在產(chǎn)生對象前就初始化了焙压,這也就是可以使用類名訪問靜態(tài)變量的原因鸿脓。
②分配內(nèi)存: 在類加載檢查通過后,接下來虛擬機(jī)將為新生對象分配內(nèi)存涯曲。對象所需的內(nèi)存大小在類加載完成后便可確定野哭,為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來。分配方式有 “指針碰撞” 和 “空閑列表” 兩種幻件,選擇那種分配方式由 Java 堆是否規(guī)整決定拨黔,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
內(nèi)存分配并發(fā)問題:在創(chuàng)建對象的時(shí)候有一個(gè)很重要的問題绰沥,就是線程安全篱蝇,因?yàn)樵趯?shí)際開發(fā)過程中,創(chuàng)建對象是很頻繁的事情徽曲,作為虛擬機(jī)來說零截,必須要保證線程是安全的,通常來講秃臣,虛擬機(jī)采用兩種方式來保證線程安全:
?1)CAS+失敗重試:CAS 是樂觀鎖的一種實(shí)現(xiàn)方式涧衙。所謂樂觀鎖就是哪工,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試弧哎,直到成功為止雁比。虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性。
2)TLAB:為每一個(gè)線程預(yù)先在 Eden 區(qū)分配一塊兒內(nèi)存撤嫩,JVM 在給線程中的對象分配內(nèi)存時(shí)偎捎,首先在 TLAB 分配,當(dāng)對象大于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已用盡時(shí)序攘,再采用上述的 CAS 進(jìn)行內(nèi)存分配
③初始化零值: 內(nèi)存分配完成后茴她,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭),這一步操作保證了對象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用两踏,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的零值败京。
④設(shè)置對象頭: 初始化零值完成之后兜喻,虛擬機(jī)要對對象進(jìn)行必要的設(shè)置梦染,例如這個(gè)對象是那個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息朴皆、對象的哈希嗎帕识、對象的 GC 分代年齡等信息。 這些信息存放在對象頭中遂铡。 另外肮疗,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等扒接,對象頭會(huì)有不同的設(shè)置方式伪货。
⑤執(zhí)行 init 方法: 在上面工作都完成之后,從虛擬機(jī)的視角來看钾怔,一個(gè)新的對象已經(jīng)產(chǎn)生了碱呼,但從 Java 程序的視角來看,對象創(chuàng)建才剛開始宗侦,<init> 方法還沒有執(zhí)行愚臀,所有的字段都還為零。所以一般來說矾利,執(zhí)行 new 指令之后會(huì)接著執(zhí)行 <init>方法姑裂,把對象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對象才算完全產(chǎn)生出來男旗。