類的初始化與實(shí)例化
一個(gè) Java 對(duì)象的創(chuàng)建過(guò)程往往包括類的初始化 和 實(shí)例化 兩個(gè)階段。
Java 規(guī)范規(guī)定一個(gè)對(duì)象在可以被使用之前必須要被正確地初始化。在類初始化過(guò)程中或初始化完畢后,根據(jù)具體情況才會(huì)去對(duì)類進(jìn)行實(shí)例化。在實(shí)例化一個(gè)對(duì)象時(shí),JVM 首先會(huì)檢查相關(guān)類型是否已經(jīng)加載并初始化,如果沒(méi)有,則 JVM 立即進(jìn)行加載并調(diào)用類構(gòu)造器完成類的初始化。
Java 對(duì)象的創(chuàng)建方式
一個(gè)對(duì)象在可以被使用之前必須要被正確地實(shí)例化。在 Java 程序中,有多種方法可以創(chuàng)建對(duì)象,最直接的一種就是使用 new 關(guān)鍵字來(lái)調(diào)用一個(gè)類的構(gòu)造函數(shù)顯式地創(chuàng)建對(duì)象。這種方式是由執(zhí)行類的實(shí)例創(chuàng)建表達(dá)式創(chuàng)建對(duì)象。除此之外,還可以使用反射機(jī)制 (Class 類的 newInstance 方法、Constructor 類的newInstance 方法)、使用 Clone 方法、使用反序列化等方式創(chuàng)建對(duì)象。
使用 new 關(guān)鍵字創(chuàng)建對(duì)象
這是最常見、最簡(jiǎn)單的創(chuàng)建對(duì)象的方式,通過(guò)這種方式可以調(diào)用任意的構(gòu)造函數(shù)(無(wú)參的和有參的)創(chuàng)建對(duì)象。
使用 Class 類的 newInstance 方法 (反射機(jī)制) 。事實(shí)上 Class 類的 newInstance 方法內(nèi)部調(diào)用的是 Constructor 類的 newInstance 方法,相當(dāng)于是調(diào)用無(wú)參的構(gòu)造器創(chuàng)建對(duì)象。
使用 Constructor 類的 newInstance 方法 (反射機(jī)制) 。該方法和 Class 類中的 newInstance 方法類似,不同的是 Constructor 類的 newInstance 方法可以調(diào)用有參數(shù)的和私有的構(gòu)造函數(shù)。
使用Clone方法創(chuàng)建對(duì)象
調(diào)用一個(gè)對(duì)象的 clone 方法,JVM 都會(huì)創(chuàng)建一個(gè)新的、一樣的對(duì)象。特別需要說(shuō)明的是,用 clone 方法創(chuàng)建對(duì)象的過(guò)程中并不會(huì)調(diào)用任何構(gòu)造函數(shù)。如何使用 clone 方法以及淺克隆/深克隆機(jī)制。簡(jiǎn)單而言,要想使用 clone 方法,就必須先實(shí)現(xiàn) Cloneable 接口并實(shí)現(xiàn)其定義的 clone 方法,這也是原型模式的應(yīng)用。
使用 (反) 序列化機(jī)制創(chuàng)建對(duì)象
當(dāng)反序列化一個(gè)對(duì)象時(shí),JVM會(huì)創(chuàng)建一個(gè)單獨(dú)的對(duì)象,在此過(guò)程中,JVM并不會(huì)調(diào)用任何構(gòu)造函數(shù)。為了反序列化一個(gè)對(duì)象,對(duì)應(yīng)的類需要實(shí)現(xiàn) Serializable 接口。
從 Java 虛擬機(jī)層面看,除了使用 new 關(guān)鍵字創(chuàng)建對(duì)象的方式外,其他方式全部都是通過(guò)轉(zhuǎn)變?yōu)?invokevirtual 指令直接創(chuàng)建對(duì)象的。
Java 對(duì)象的創(chuàng)建過(guò)程
當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),虛擬機(jī)就會(huì)為其分配內(nèi)存來(lái)存放對(duì)象自己的實(shí)例變量及其繼承父類的實(shí)例變量 (即使繼承超類的實(shí)例變量有可能被隱藏也會(huì)被分配空間) 。在為這些實(shí)例變量分配內(nèi)存的同時(shí),這些實(shí)例變量也會(huì)被賦予默認(rèn)值。在內(nèi)存分配完成之后,Java 虛擬機(jī)就會(huì)開始對(duì)新創(chuàng)建的對(duì)象進(jìn)行初始化。在 Java 對(duì)象初始化過(guò)程中,主要涉及三種執(zhí)行對(duì)象初始化的結(jié)構(gòu),分別是實(shí)例變量初始化、實(shí)例代碼塊初始化以及構(gòu)造函數(shù)初始化。
實(shí)例變量初始化與實(shí)例代碼塊初始化
在定義(聲明)實(shí)例變量的同時(shí),可以直接對(duì)實(shí)例變量進(jìn)行賦值或者使用實(shí)例代碼塊對(duì)其進(jìn)行賦值。如果以這兩種方式為實(shí)例變量進(jìn)行初始化,那么它們將在構(gòu)造函數(shù)執(zhí)行之前完成這些初始化操作。實(shí)際上,如果對(duì)實(shí)例變量直接賦值或者使用實(shí)例代碼塊賦值,那么編譯器會(huì)將其中的代碼放到類的構(gòu)造函數(shù)中去,并且這些代碼會(huì)被放在對(duì)超類構(gòu)造函數(shù)的調(diào)用語(yǔ)句之后 (構(gòu)造函數(shù)的第一條語(yǔ)句必須是超類構(gòu)造函數(shù)的調(diào)用語(yǔ)句) ,構(gòu)造函數(shù)本身的代碼之前。
特別需要注意的是,Java 是按照先后順序來(lái)執(zhí)行實(shí)例變量初始化和實(shí)例初始化器中的代碼,并且不允許順序靠前的實(shí)例代碼塊初始化在其后面定義的實(shí)例變量。這么做是為了保證一個(gè)變量在被使用之前已經(jīng)被正確地初始化。
構(gòu)造函數(shù)初始化
實(shí)例變量初始化與實(shí)例代碼塊初始化總是發(fā)生在構(gòu)造函數(shù)初始化之前。Java 中的每一個(gè)類中都至少會(huì)有一個(gè)構(gòu)造函數(shù),如果沒(méi)有顯式定義構(gòu)造函數(shù),那么 JVM 會(huì)為它提供一個(gè)默認(rèn)無(wú)參的構(gòu)造函數(shù)。在編譯生成的字節(jié)碼中,這些構(gòu)造函數(shù)會(huì)被命名成 () 方法 (參數(shù)列表與 Java 語(yǔ)言中構(gòu)造函數(shù)的參數(shù)列表相同) 。Java 要求在實(shí)例化類之前,必須先實(shí)例化其超類,以保證所創(chuàng)建實(shí)例的完整性。
事實(shí)上,這一點(diǎn)是在構(gòu)造函數(shù)中保證的:Java 強(qiáng)制要求除 Object 類 (Object 是 Java 的頂層類,沒(méi)有超類) 之外所有類的構(gòu)造函數(shù)中的第一條語(yǔ)句必須是超類構(gòu)造函數(shù)的調(diào)用語(yǔ)句或者是類中定義的其他的構(gòu)造函數(shù)。如果既沒(méi)有調(diào)用其他的構(gòu)造函數(shù),也沒(méi)有顯式調(diào)用超類的構(gòu)造函數(shù),那么編譯器會(huì)自動(dòng)生成一個(gè)對(duì)超類構(gòu)造函數(shù)的調(diào)用。
如果顯式調(diào)用超類的構(gòu)造函數(shù),那么該調(diào)用必須放在構(gòu)造函數(shù)所有代碼的最前面。正因?yàn)槿绱?,Java 才可以使得一個(gè)對(duì)象在初始化之前其所有的超類都被初始化完成,并保證創(chuàng)建一個(gè)完整的對(duì)象出來(lái)。特別地,如果在一個(gè)構(gòu)造函數(shù)中調(diào)用另外一個(gè)構(gòu)造函數(shù)則不能顯式調(diào)用超類的構(gòu)造函數(shù),而且要另一個(gè)構(gòu)造函數(shù)放在構(gòu)造函數(shù)所有代碼的最前面。
Java 通過(guò)對(duì)構(gòu)造函數(shù)作出上述限制保證一個(gè)類的實(shí)例能夠在被使用之前正確地初始化。
1.Java普通對(duì)象的創(chuàng)建
這里討論的僅僅是普通Java對(duì)象,不包含數(shù)組和Class對(duì)象。
1.1new指令
虛擬機(jī)遇到一條new指令時(shí),首先去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過(guò)。如果沒(méi)有,那么須先執(zhí)行相應(yīng)的類加載過(guò)程。
1.2分配內(nèi)存
接下來(lái)虛擬機(jī)將為新生代對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存的大小在類加載完成后便可完全確定。分配方式有“指針碰撞(Bump the Pointer)”和“空閑列表(Free List)”兩種方式,具體由所采用的垃圾收集器是否帶有壓縮整理功能決定。
1.3初始化
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。
1.4對(duì)象的初始設(shè)置
接下來(lái)虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭(Object Header)之中。根據(jù)虛擬機(jī)當(dāng)前的運(yùn)行狀態(tài)的不同,如對(duì)否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式。
1.5
在上面的工作都完成了之后,從虛擬機(jī)的角度看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但是從Java程序的角度看,對(duì)象創(chuàng)建才剛剛開始—
2.Java對(duì)象內(nèi)存布局
在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為3塊區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)、對(duì)其填充(Padding)。
2.1對(duì)象頭
HotSpot虛擬機(jī)的對(duì)象頭包含兩部分信息,第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等。
對(duì)象的另一部分類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例(并不是所有的虛擬機(jī)實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類型指針,也就是說(shuō),查找對(duì)象的元數(shù)據(jù)信息并不一定要經(jīng)過(guò)對(duì)象本身)。
如果對(duì)象是一個(gè)Java數(shù)組,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)。
元數(shù)據(jù):描述數(shù)據(jù)的數(shù)據(jù)。對(duì)數(shù)據(jù)及信息資源的描述信息。在Java中,元數(shù)據(jù)大多表示為注解。
2.2實(shí)例數(shù)據(jù)
實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也是在程序代碼中定義的各種類型的字段內(nèi)容,無(wú)論從父類繼承下來(lái)的,還是在子類中定義的,都需要記錄起來(lái)。這部分的存儲(chǔ)順序會(huì)虛擬機(jī)默認(rèn)的分配策略參數(shù)和字段在Java源碼中定義的順序影響(相同寬度的字段總是被分配到一起)。
2.3對(duì)齊填充
對(duì)齊填充部分并不是必然存在的,也沒(méi)有特別的含義,它僅僅起著占位符的作用。由于HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象的起始地址必須是8字節(jié)的整數(shù)倍,也就是說(shuō),對(duì)象的大小必須是8字節(jié)的整數(shù)倍。而對(duì)象頭部分正好是8字節(jié)的倍數(shù)(1倍或者2倍),因此,當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí),就需要通過(guò)對(duì)齊填充來(lái)補(bǔ)全。
大家都知道,java使用new 關(guān)鍵字進(jìn)行對(duì)象的創(chuàng)建,但這只是從語(yǔ)言層次上理解了對(duì)象的創(chuàng)建,下邊我們從jvm的角度來(lái)看看,對(duì)象是怎么被創(chuàng)建出來(lái)的,即對(duì)象的創(chuàng)建過(guò)程。
對(duì)象的創(chuàng)建大概分為以下幾步:
1:檢查類是否已經(jīng)被加載;
2:為對(duì)象分配內(nèi)存空間;
3:為對(duì)象字段設(shè)置零值;
4:設(shè)置對(duì)象頭;
5:執(zhí)行構(gòu)造方法。
第一步,當(dāng)程序遇到new 關(guān)鍵字時(shí),首先會(huì)去運(yùn)行時(shí)常量池中查找該引用所指向的類有沒(méi)有被虛擬機(jī)加載,如果沒(méi)有被加載,那么會(huì)進(jìn)行類的加載過(guò)程,如果已經(jīng)被加載,那么進(jìn)行下一步,為對(duì)象分配內(nèi)存空間;
第二步,加載完類之后,需要在堆內(nèi)存中為該對(duì)象分配一定的空間,該空間的大小在類加載完成時(shí)就已經(jīng)確定下來(lái)了,這里多說(shuō)一點(diǎn),為對(duì)象分配內(nèi)存空間有兩種方式:
(1)第一種是jvm將堆區(qū)抽象為兩塊區(qū)域,一塊是已經(jīng)被其他對(duì)象占用的區(qū)域,另一塊是空白區(qū)域,中間通過(guò)一個(gè)指針進(jìn)行標(biāo)注,這時(shí)只需要將指針向空白區(qū)域移動(dòng)相應(yīng)大小空間,就完成了內(nèi)存的分配,當(dāng)然這種劃分的方式要求虛擬機(jī)的對(duì)內(nèi)存是地址連續(xù)的,且虛擬機(jī)帶有內(nèi)存壓縮機(jī)制,可以在內(nèi)存分配完成時(shí)壓縮內(nèi)存,形成連續(xù)地址空間,這種分配內(nèi)存方式成為“指針碰撞”,但是很明顯,這種方式也存在一個(gè)比較嚴(yán)重的問(wèn)題,那就是多線程創(chuàng)建對(duì)象時(shí),會(huì)導(dǎo)致指針劃分不一致的問(wèn)題,例如A線程剛剛將指針移動(dòng)到新位置,但是B線程之前讀取到的是指針之前的位置,這樣劃分內(nèi)存時(shí)就出現(xiàn)不一致的問(wèn)題,解決這種問(wèn)題,虛擬機(jī)采用了循環(huán)CAS操作來(lái)保證內(nèi)存的正確劃分;
(2)第二種也是為了解決第一種分配方式的不足而創(chuàng)建的方式,多線程分配內(nèi)存時(shí),虛擬機(jī)為每個(gè)線程分配了不同的空間,這樣每個(gè)線程在分配內(nèi)存時(shí)只是在自己的空間中操作,從而避免了上述問(wèn)題,不需要同步。當(dāng)然,當(dāng)線程自己的空間用完了才需要需申請(qǐng)空間,這時(shí)候需要進(jìn)行同步鎖定。為每個(gè)線程分配的空間稱為“本地線程分配緩沖(TLAB)”,是否啟用TLAB需要通過(guò) -XX:+/-UseTLAB參數(shù)來(lái)設(shè)定。
第三步,分配完內(nèi)存后,需要對(duì)對(duì)象的字段進(jìn)行零值初始化,對(duì)象頭除外,零值初始化意思就是對(duì)對(duì)象的字段賦0值,或者null值,這也就解釋了為什么這些字段在不需要進(jìn)程初始化時(shí)候就能直接使用;
第四步,這里,虛擬機(jī)需要對(duì)這個(gè)將要?jiǎng)?chuàng)建出來(lái)的對(duì)象,進(jìn)行信息標(biāo)記,包括是否為新生代/老年代,對(duì)象的哈希碼,元數(shù)據(jù)信息,這些標(biāo)記存放在對(duì)象頭信息中,對(duì)象頭非常復(fù)雜,這里不作解釋,可以另行百度;
第五步,也就是最后一步,執(zhí)行對(duì)象的構(gòu)造方法,這里做的操作才是程序員真正想做的操作,例如初始化其他對(duì)象啊等等操作,至此,對(duì)象創(chuàng)建成功。
java中個(gè),創(chuàng)建一個(gè)對(duì)象需要經(jīng)過(guò)五步,分別是類加載檢查、分配內(nèi)存、初始化零值、設(shè)置對(duì)象頭和執(zhí)行初始化init()。
成都創(chuàng)新互聯(lián)公司長(zhǎng)期為上千余家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為陜西企業(yè)提供專業(yè)的成都做網(wǎng)站、網(wǎng)站設(shè)計(jì),陜西網(wǎng)站改版等技術(shù)服務(wù)。擁有十余年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。