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

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
當(dāng)前名稱:Java對象的創(chuàng)建過程-創(chuàng)新互聯(lián)
URL分享:http://chinadenli.net/article20/diihjo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、網(wǎng)站設(shè)計公司、用戶體驗、網(wǎng)頁設(shè)計公司、網(wǎng)站建設(shè)、微信小程序
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)