91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java 對象的創建過程

發布時間:2020-07-05 14:25:21 來源:網絡 閱讀:508 作者:專注地一哥 欄目:編程語言

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

  1. 類加載檢查
    在java中,new一個對象的時候,java虛擬機會首先去檢查這個指令的參數是否能在常量池中找到這個對象對應的類的符號引用,檢查這個符號引用代表的類是否被類加載器加載、解析和初始化;如果沒有,則必須要進行類加載。
  2. 分配內存
    在類加載之后,虛擬機會為將要的ThinkMarkets代理申請www.kaifx.cn/broker/thinkmarkets.html創建的對象分配內存,對象所需內存的大小在類加載完成便可完全確定,給對象分配內存是要在java堆中劃分出一塊確定的內存。在java堆內存分配一般有兩種方式,指針碰撞和空閑列表。
    (1)指針碰撞
    在java堆規整的情況下,適合采用指針碰撞方式。用過的內存全部整合到以便,沒有用過的內存放在另外一邊,中間有一個分界值指針,用來將用過的內存與空閑內存分隔開來,當給新生對象分配內存時,指針便會向空閑內存區域移動。
    (2)空閑列表
    在java堆不規整的情況下,適合采用空閑列表方式。這種方式中,java虛擬機會維護一個列表,該列表是記錄內存的塊是否是可用的,當為新生對象分配內存的時候,會找一塊足夠大的內存分配給新生對象,之后更新這個列表。
    java堆是否規整由java虛擬機采用的垃圾收集器是否有壓縮整理的功能決定。
  3. 初始化零值
    在給新生對象分配完內存完之后,虛擬機需要將分配到的內存空間都初始化為零值,這步操作保證了對象的實例字段在java代碼中可以不賦初值就可以直接使用。
  4. 設置對象頭
    初始化零值之后,要對新生對象設置對象頭。對象頭中包含類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。根據虛擬機當前運行狀態的不同,對象頭也會有不同的設置方式。
  5. 初始化(執行init方法)
    在給對象設置完對象頭之后,虛擬機已經將一個對象產生了,此時,方法沒有執行,對象的所有字段都為零值,零值的對象在程序中沒有使用意義,只有初始化之后,對象才能真正體現出作用。
    此外,虛擬機創建java對象的時候,要保障線程的安全,虛擬機采用兩種方式來保證線程安全。
    Java創建對象的過程
    簡單記錄一下Java創建對象的過程,就是new一個對象的時候發生了哪些事情。Java程序執行的過程在此不作說明,對象的創建過程只是程序執行過程的一部分。有關整個程序執行的過程,等熟悉了虛擬機之后在作說明。
    對象創建過程簡述
    Java中對象的創建就是在堆上分配內存空間的過程,此處說的對象創建僅限于new關鍵字創建的普通Java對象,不包括數組對象的創建。
    大致過程如下:
    檢測類是否被加載
    為對象分配內存
    為分配的內存空間初始化零值
    對對象進行其他設置
    執行init方法
    檢測類是否被加載
    當虛擬機執行到new時,會先去常量池中查找這個類的符號引用。如果能找到符號引用,說明此類已經被加載到方法區(方法區存儲虛擬機已經加載的類的信息),可以繼續執行;如果找不到符號引用,就會使用類加載器執行類的加載過程,類加載完成后繼續執行。
    為對象分配內存
    類加載完成以后,虛擬機就開始為對象分配內存,此時所需內存的大小就已經確定了。只需要在堆上分配所需要的內存即可。
    具體的分配內存有兩種情況:第一種情況是內存空間絕對規整,第二種情況是內存空間是不連續的。
    對于內存絕對規整的情況相對簡單一些,虛擬機只需要在被占用的內存和可用空間之間移動指針即可,這種方式被稱為指針碰撞。
    對于內存不規整的情況稍微復雜一點,這時候虛擬機需要維護一個列表,來記錄哪些內存是可用的。分配內存的時候需要找到一個可用的內存空間,然后在列表上記錄下已被分配,這種方式成為空閑列表。
    分配內存的時候也需要考慮線程安全問題,有兩種解決方案:
    第一種是采用同步的辦法,使用CAS來保證操作的原子性。
    另一種是每個線程分配內存都在自己的空間內進行,即是每個線程都在堆中預先分配一小塊內存,稱為本地線程分配緩沖(TLAB),分配內存的時候再TLAB上分配,互不干擾。
    為分配的內存空間初始化零值
    對象的內存分配完成后,還需要將對象的內存空間都初始化為零值,這樣能保證對象即使沒有賦初值,也可以直接使用。
    對對象進行其他設置
    分配完內存空間,初始化零值之后,虛擬機還需要對對象進行其他必要的設置,設置的地方都在對象頭中,包括這個對象所屬的類,類的元數據信息,對象的hashcode,GC分代年齡等信息。
    執行init方法
    執行完上面的步驟之后,在虛擬機里這個對象就算創建成功了,但是對于Java程序來說還需要執行init方法才算真正的創建完成,因為這個時候對象只是被初始化零值了,還沒有真正的去根據程序中的代碼分配初始值,調用了init方法之后,這個對象才真正能使用。
    到此為止一個對象就產生了,這就是new關鍵字創建對象的過程。過程如下:
    檢測類是否被加載–>為對象分配內存空間–>初始零值–>進行必要的設置–>調用init方法進行初始化。
    對象的創建過程:
    類加載檢查-->分配內存-->初始化零值-->設置對象頭-->執行init方法
    1、類加載檢查:虛擬機遇到一條new指令時,先檢查這個指令的參數能否在常量池中定位到一個類的符號引用,并檢查這個符號引用代表的類是否已被ji加載、解析和初始化過。如果沒有,則先進行類的加載過程。
    2、分配內存:有兩種方式
    指針碰撞:假設Java堆中的內存是規整的,用過的內存在一邊,空閑的在另一邊,中間有一個指針作為分界點的指示器,所分配的內存就把那個指針向空閑那邊挪動一段與對象大小相等的距離。
    空閑列表:如果Java堆中的內存不是規整的,虛擬機必須維護一個列表,記錄哪些內存塊可用的,分配時從列表中找到一塊足夠大的空間劃分給對象,并更新列表的記錄。
    在劃分可用空間時,會遇到線程安全的問題。解決這個問題有兩種方案。第一種:對分配內存空間的動作進行同步處理--虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性。另一種是把內存分配的動作安裝現場劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存,稱為本地線程分配緩沖(TLAB)。那個線程需要分配內存,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時,才需要同步鎖定。是否使用TLAB,-XX:+UseTLAB參數來設定。
    3、初始化零值 將分配到的內存空間都初始化為零值,如果用TLAB,則在TLAB分配時初始化為零值。
    4、設置對象頭:主要設置類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。
    5、執行init方法初始化。
    類加載過程
    當JVM第一次要使用一個類的時候,需要加載這個類;
    首先根據classpath的配到硬盤上找這個類的class文件(如果沒有配置classpath,就到當前位置找);
    如果找到這個class,就加載到方法區;
    a) 分別將這個類的靜態成員加載到靜態區域,非靜態成員加載到非靜態區域;
    b) 在靜態區域為所有靜態成員變量分配空間,賦默認值;
    c) 為所有靜態成員變量顯示賦值
    d) 執行所有靜態代碼塊
    (c和d具體順序是按照代碼書寫的先后順序)
    等到靜態代碼塊都執行完畢,類加載完成;
    對象的創建過程
    JVM遇到new關鍵字,首先回去堆內存中開辟空間;
    為所有非靜態的成員變量分配空間,賦默認值;
    調用相應的構造函數進棧執行;
    構造函數執行時首先要執行隱式三步:
    a) super():調用父類構造函數 // 如果第一行是this就調用this
    b) 給對象中所有非靜態成員變量顯示賦值;
    c) 執行構造代碼塊;
    (b和c具體執行順序,是按照代碼書寫的先后順序)
    隱式三步執行結束后,開始執行構造函數中的代碼;
    等到構造函數結束出棧,對象才算創建完成
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

黔东| 冀州市| 峨山| 秦皇岛市| 游戏| 夏津县| 乐清市| 灵丘县| 新乡市| 靖州| 海宁市| 新丰县| 富平县| 溧阳市| 潼南县| 贺兰县| 麦盖提县| 库伦旗| 阿巴嘎旗| 逊克县| 商都县| 准格尔旗| 巴林右旗| 延庆县| 哈巴河县| 凤山县| 舒城县| 黄山市| 民和| 图木舒克市| 巫溪县| 文成县| 常宁市| 商都县| 仁怀市| 梅州市| 谢通门县| 滨海县| 眉山市| 恭城| 溧阳市|