真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Java對(duì)象創(chuàng)建的流程

本篇內(nèi)容介紹了“Java對(duì)象創(chuàng)建的流程”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、小程序設(shè)計(jì)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了康縣免費(fèi)建站歡迎大家使用!

類初始化

類的創(chuàng)建的觸發(fā)操作

Java對(duì)象創(chuàng)建的流程

在Java代碼中,有很多行為可以引起對(duì)象的創(chuàng)建,最為直觀的一種就是使用new關(guān)鍵字來調(diào)用一個(gè)類的構(gòu)造函數(shù)顯式地創(chuàng)建對(duì)象,這種方式在Java規(guī)范中被稱為 :由執(zhí)行類實(shí)例創(chuàng)建表達(dá)式而引起的對(duì)象創(chuàng)建。除此之外,我們還可以使用反射機(jī)制(Class類的newInstance方法、使用Constructor類的newInstance方法)、使用Clone方法、使用反序列化等方式創(chuàng)建對(duì)象。

使用new關(guān)鍵字創(chuàng)建對(duì)象

這是我們最常見的也是最簡單的創(chuàng)建對(duì)象的方式,通過這種方式我們可以調(diào)用任意的構(gòu)造函數(shù)(無參的和有參的)去創(chuàng)建對(duì)象。比如:

Student student = new Student();
使用Class類的newInstance方法(反射機(jī)制)

我們也可以通過Java的反射機(jī)制使用Class類的newInstance方法來創(chuàng)建對(duì)象,事實(shí)上,這個(gè)newInstance方法調(diào)用無參的構(gòu)造器創(chuàng)建對(duì)象,比如:

Student student2 = (Student)Class.forName("Student類全限定名").newInstance();
Student stu = Student.class.newInstance();
使用Constructor類的newInstance方法(反射機(jī)制)

java.lang.relect.Constructor類里也有一個(gè)newInstance方法可以創(chuàng)建對(duì)象,該方法和Class類中的newInstance方法很像,但是相比之下,Constructor類的newInstance方法更加強(qiáng)大些,我們可以通過這個(gè)newInstance方法調(diào)用有參數(shù)的和私有的構(gòu)造函數(shù),比如:

public class Student {
    private int id;
    public Student(Integer id) {
        this.id = id;
    }
    public static void main(String[] args) throws Exception {
        Constructor constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);
    }
}

使用newInstance方法的這兩種方式創(chuàng)建對(duì)象使用的就是Java的反射機(jī)制,事實(shí)上Class的newInstance方法內(nèi)部調(diào)用的也是Constructor的newInstance方法。

使用Clone方法創(chuàng)建對(duì)象

無論何時(shí)我們調(diào)用一個(gè)對(duì)象的clone方法,JVM都會(huì)幫我們創(chuàng)建一個(gè)新的、一樣的對(duì)象,特別需要說明的是,用clone方法創(chuàng)建對(duì)象的過程中并不會(huì)調(diào)用任何構(gòu)造函數(shù)。簡單而言,要想使用clone方法,我們就必須先實(shí)現(xiàn)Cloneable接口并實(shí)現(xiàn)其定義的clone方法,這也是原型模式的應(yīng)用。比如:

public class Student implements Cloneable{

    private int id;

    public Student(Integer id) {
        this.id = id;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }

    public static void main(String[] args) throws Exception {

        Constructor constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);
        Student stu4 = (Student) stu3.clone();
    }
}
使用(反)序列化機(jī)制創(chuàng)建對(duì)象

當(dāng)我們反序列化一個(gè)對(duì)象時(shí),JVM會(huì)給我們創(chuàng)建一個(gè)單獨(dú)的對(duì)象,在此過程中,JVM并不會(huì)調(diào)用任何構(gòu)造函數(shù)。為了反序列化一個(gè)對(duì)象,我們需要讓我們的類實(shí)現(xiàn)Serializable接口,比如

public class Student implements Cloneable, Serializable {

    private int id;

    public Student(Integer id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }

    public static void main(String[] args) throws Exception {

        Constructor constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);

        // 寫對(duì)象
        ObjectOutputStream output = new ObjectOutputStream(
                new FileOutputStream("student.bin"));
        output.writeObject(stu3);
        output.close();

        // 讀對(duì)象
        ObjectInputStream input = new ObjectInputStream(new FileInputStream(
                "student.bin"));
        Student stu5 = (Student) input.readObject();
        System.out.println(stu5);
    }
}
使用Unsafe類創(chuàng)建對(duì)象

Unsafe類使Java擁有了像C語言的指針一樣操作內(nèi)存空間的能力,同時(shí)也帶來了指針的問題。過度的使用Unsafe類會(huì)使得出錯(cuò)的幾率變大,因此Java官方并不建議使用的,官方文檔也幾乎沒有。Oracle正在計(jì)劃從Java 9中去掉Unsafe類,如果真是如此影響就太大了

我們無法直接創(chuàng)建Unsafe對(duì)象。這里我們使用反射方法得到

private static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            return unsafe;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
  }

拿到這個(gè)對(duì)象后,調(diào)用其中的native方法allocateInstance 創(chuàng)建一個(gè)對(duì)象實(shí)例

Object event = unsafe.allocateInstance(Test.class);

從Java虛擬機(jī)層面看,除了使用new關(guān)鍵字創(chuàng)建對(duì)象((invokespecial))的方式外,其他方式全部都是通過轉(zhuǎn)變?yōu)閕nvokevirtual指令直接創(chuàng)建對(duì)象的。

類的初始化與實(shí)例化

概念介紹

Java對(duì)象的創(chuàng)建過程往往包括類初始化和類實(shí)例化兩個(gè)階段。類的初始化在前、類的實(shí)例化在后。

注意:這與spring的bean正好相反,spring的bean的生命周期,主要是先進(jìn)行實(shí)例化java對(duì)象,然后在進(jìn)行操作屬性、最后進(jìn)行初始化,這里初始化并不是java對(duì)象的初始化,而是spring的參數(shù)的初始化(initMethod、afterPropertiesSet)等。(@PostConstruct是前置攔截初始化方法)。

類的初始化

主要職責(zé):

類的構(gòu)造器調(diào)用(),初始化相關(guān)靜態(tài)代碼塊以及靜態(tài)變量的賦值

對(duì)象在可以被使用之前必須要被正確地初始化,這一點(diǎn)是Java規(guī)范規(guī)定的在實(shí)例化一個(gè)對(duì)象時(shí),JVM首先會(huì)檢查相關(guān)類型是否已經(jīng)加載并初始化,如果沒有,則JVM立即進(jìn)行加載并調(diào)用類構(gòu)造器完成類的初始化。

注意:可以看到類的初始化主要到類構(gòu)造器變量結(jié)束執(zhí)行的時(shí)間點(diǎn)。

類的實(shí)例化

主要職責(zé):實(shí)例的構(gòu)造器調(diào)用()、分配內(nèi)存、屬性值得定制化賦值機(jī)制。

類的實(shí)例化本身意義就是對(duì)象的概念,其實(shí)就是實(shí)例化對(duì)應(yīng)的對(duì)象的過程

實(shí)例對(duì)象內(nèi)存的分配、實(shí)例對(duì)象參數(shù)的默認(rèn)初始化+實(shí)例對(duì)象參數(shù)的實(shí)例化(就是按開發(fā)要求的實(shí)現(xiàn)調(diào)用,例如調(diào)用構(gòu)造器等)

此時(shí)一般處于在裝載階段的初始化完成之后,使用之前的階段,接下來就要進(jìn)行類的實(shí)例化操作

  • 類初始化過程中或初始化完畢后,根據(jù)具體情況才會(huì)去對(duì)類進(jìn)行實(shí)例化,首先會(huì)有一下幾個(gè)步驟:

    • java虛擬機(jī)就會(huì)為其分配內(nèi)存來存放自己及其從父類繼承過來的實(shí)例變量

    • 為這些實(shí)例變量分配內(nèi)存的同時(shí),這些實(shí)例變量先會(huì)被賦予默認(rèn)值(零值)【這個(gè)零值與加載階段中的準(zhǔn)備很相似,就是先賦值語義級(jí)別的默認(rèn)值,而并非參數(shù)真正的初始化】

    • 在內(nèi)存分配完成之后調(diào)用方法,Java虛擬機(jī)執(zhí)行構(gòu)造代碼塊、構(gòu)造方法等,方法參數(shù)執(zhí)行等。才會(huì)對(duì)新創(chuàng)建的對(duì)象賦予我們程序給定的值

小結(jié):創(chuàng)建一個(gè)對(duì)象包含下面兩個(gè)過程:

  • 類構(gòu)造器完成類初始化(賦予靜態(tài)變量默認(rèn)值)

  • 類實(shí)例化(分配內(nèi)存、賦予默認(rèn)值、執(zhí)行定制化賦值操作)


類實(shí)例化過程

檢測類是否被加載

Java虛擬機(jī)遇到一條new指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過**。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程**。

為新生對(duì)象分配內(nèi)存

在類加載并完成類初始化之后,接下來虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需內(nèi)存的大小在類加載及初始化完成后便可完全確定。


確定對(duì)象內(nèi)存大小

對(duì)象的大小在類加載完成后就已經(jīng)確定,對(duì)象在內(nèi)存中可以分為三塊。

對(duì)象頭

大小確定 與類無關(guān) 與操作系統(tǒng)有關(guān),包括標(biāo)記字段和類型指針

實(shí)例數(shù)據(jù)

即使父類的實(shí)例字段被子類覆蓋或者被private修飾,都照樣為其分配內(nèi)存,相同寬度的字段會(huì)分配在一起,其次,父類的字段在子類之前賦值和初始化

對(duì)齊填充

滿足虛擬機(jī)對(duì)8的倍數(shù)的要求


  • 對(duì)象分配內(nèi)存的方式。

    • 假設(shè)Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那么分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離,這種分配方式稱為“指針碰撞”

    • 如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒有辦法簡單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄,這種分配方式稱為“空閑列表”

選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。

  • 對(duì)象創(chuàng)建在虛擬機(jī)執(zhí)行的過程中是非常頻繁的行為,僅僅修改一個(gè)指針?biāo)赶虻奈恢?,在并發(fā)情況下不是線程安全的。因此也有兩種解決方案

    • 使用CAS并配上失敗重試的方式保證更新操作的原子性

    • (TLAB)給每一個(gè)線程在Java堆中預(yù)先分配線程私有分配緩沖區(qū),哪個(gè)線程需要分配內(nèi)存,只要在線程私有分配緩沖區(qū)中分配即可以。

Java對(duì)象創(chuàng)建的流程

初始化零值

將分配到的內(nèi)存空間初始化零值,這保證了實(shí)例字段不賦值可以直接使用。如果使用了TLAB,這一步可以提前到TLAB分配的時(shí)候進(jìn)行。

內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。

Java對(duì)象創(chuàng)建的流程

進(jìn)行必要的對(duì)象頭設(shè)置

虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的GC分代年齡等信息。這些信息存放在對(duì)象的對(duì)象頭之中,主要負(fù)責(zé)的5個(gè)點(diǎn)。

  • 對(duì)象是哪個(gè)類的實(shí)例;

  • 如何找到類的元數(shù)據(jù)信息;

  • 對(duì)象的哈希碼;

  • 對(duì)象的GC分代年齡信息;

  • 鎖的標(biāo)識(shí)等;

執(zhí)行完以上步驟,從虛擬機(jī)角度,一個(gè)對(duì)象已經(jīng)產(chǎn)生了,但是對(duì)于java程序而言,構(gòu)造函數(shù)還沒有開始執(zhí)行。接下來按照構(gòu)造函數(shù)的要求,對(duì)對(duì)象進(jìn)行初始化即可。

Java對(duì)象創(chuàng)建的流程

  • 對(duì)象頭主要包含兩類信息。第一類是用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏向線程ID、偏向時(shí)間戳等。對(duì)象頭的另外一部分是類型指針,即對(duì)象指向它的類型元數(shù)據(jù)的指針。

  • 類型數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,即程序代碼中定義的各種類型的字段內(nèi)容。

  • 對(duì)齊填充:任何對(duì)象的大小都必須是8字節(jié)的整數(shù)倍。

對(duì)象的訪問定位

使用句柄訪問的話,Java堆中將可能會(huì)劃分出來一塊內(nèi)存來作為句柄池。Reference變量中存放的是句柄池的地址,句柄池中存放有到對(duì)象實(shí)例數(shù)據(jù)的指針以及到對(duì)象類型數(shù)據(jù)的指針。

使用直接訪問的話,reference變量中存放的是對(duì)象的實(shí)例數(shù)據(jù)、對(duì)象的實(shí)例數(shù)據(jù)中包含有到對(duì)象類型數(shù)據(jù)的指針。

執(zhí)行init方法

在上面工作都完成之后,從虛擬機(jī)的視角來看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但從Java程序的視角來看,對(duì)象創(chuàng)建才剛開始,方法還沒有執(zhí)行,所有的字段都還為零。所以一般來說,執(zhí)行new指令之后會(huì)接著執(zhí)行方法,把對(duì)象按照程序的進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來。

總結(jié)創(chuàng)建一個(gè)對(duì)象的過程

檢測類是否被加載沒有加載的先加載為新生對(duì)象分配內(nèi)存→將分配到的內(nèi)存空間都初始化為零值→**對(duì)對(duì)象進(jìn)行必要的設(shè)置(對(duì)象頭)**→執(zhí)行方法把對(duì)象進(jìn)行初始化


擴(kuò)展延伸

需要類初始化的五種情況(有且僅有這五種) 。

  • 遇到new getstatic putstatic invokestatic這四個(gè)指令時(shí),如果類沒有初始化,則會(huì)觸發(fā)類的初始化

    • 四個(gè)指令對(duì)應(yīng)的最常見的情況為使用new關(guān)鍵字實(shí)例化對(duì)象,讀取靜態(tài)變量,設(shè)置靜態(tài)變量(編譯期結(jié)果放入常量池的除外,會(huì)觸發(fā)前幾個(gè)階段) ,調(diào)用類的靜態(tài)方法。

  • 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用

  • 初始化一個(gè)類時(shí) 如果父類未初始化 則初始化父類

常見的不會(huì)觸發(fā)初始化的引用方式

  • 通過子類引用父類的靜態(tài)變量 只會(huì)初始化父類 不會(huì)初始化子類

  • 創(chuàng)建類的數(shù)組

  • 引用常量池內(nèi)的變量


加載

通過名字獲取類的二進(jìn)制流并在內(nèi)存中生成class對(duì)象。

驗(yàn)證

驗(yàn)證二進(jìn)制流的正確性和安全性 包括文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證,字節(jié)碼驗(yàn)證以及符號(hào)引用驗(yàn)證四個(gè)步驟。

準(zhǔn)備

給類變量(static)分配空間并完成初始化 注意:如果不是final變量,值均為零值。

解析

將符號(hào)引用解析為直接引用,包括類或接口的解析,字段解析,類方法解析,接口方法解析。

初始化

執(zhí)行類的()方法,這個(gè)方法由所有對(duì)靜態(tài)變量的賦值操作和所有靜態(tài)代碼塊組成,虛擬機(jī)會(huì)保證父類的()方法在子類的方法開始之前結(jié)束,并且提供線程安全的保證(類似于double check,多個(gè)線程同時(shí)初始化時(shí)只有一個(gè)線程進(jìn)入方法,其他線程阻塞,執(zhí)行完成后其他線程不會(huì)再進(jìn)入方法)

類加載器-雙親委派模型

Java推薦的類加載器的實(shí)現(xiàn)模型,除了啟動(dòng)類加載器(bootstrap classLoader)以外的所有類加載器都應(yīng)該擁有父加載器,這個(gè)關(guān)系不是通過繼承來實(shí)現(xiàn),而是通過組合的方式。類加載器收到加載請(qǐng)求時(shí),首先請(qǐng)求父加載器進(jìn)行加載,如果父加載器不能加載則調(diào)用自己的加載方法。

  • 遵從雙親委派模型:自定義類加載器時(shí)如果我們希望則重寫findClass()方法

  • 不想遵循雙親委托型:方案即重寫loadClass()方法

類加載器-分類

遵循雙親委派從上到下可以分為

  • 啟動(dòng)類加載器 (Bootstrap classLoader) 加載\lib下的指定文件名的類

  • 擴(kuò)展類加載器 (Extension classLoader) 加載\lib\ext下的類

  • 系統(tǒng)類加載器(應(yīng)用類加載器) 加載Classpath內(nèi)的類

  • 自定義類加載器

不遵循雙親委派的常見類加載器:

  • SPI (Service Provider Interface)加載器 - 線程上下文加載器 實(shí)現(xiàn)父加載器向子加載器請(qǐng)求加載

  • OSGi模塊化加載器 每個(gè)模塊擁有一個(gè)自定義類加載器 網(wǎng)狀結(jié)構(gòu)的加載過程

分配內(nèi)存

內(nèi)存分配與內(nèi)存回收緊密相關(guān),根據(jù)不同的回收策略也有不同的分配策略。

如果采用的是具有壓縮過程的垃圾回收策略,如Serial,ParNew,則Java堆中的內(nèi)存是規(guī)整的,我們只需要將內(nèi)存指針向后移內(nèi)存大小的位置即可,這種方式稱為指針碰撞(Bump the Pointer)。如果采用的回收策略沒有壓縮過程,如CMS,那虛擬機(jī)就需要維護(hù)一個(gè)列表,記錄哪些內(nèi)存是可用的,這種方式稱為空閑列表(Free List)

其次,對(duì)象創(chuàng)建也需要考慮線程安全的問題,一種方案是采用CAS+失敗重試的方法來保證線程安全,另一種方法則是為每一個(gè)線程提前分配一塊內(nèi)存,稱為本地線程分配緩沖(Thread Local Allocation Buffer , TLAB),線程創(chuàng)建對(duì)象時(shí)優(yōu)先在自己的TLAB上分配。

對(duì)象頭

對(duì)象頭包括

MarkWord 32bit/64bit 取決于操作系統(tǒng)

  • 類型指針 指向類元數(shù)據(jù)的指針

  • 數(shù)組長度 如果是數(shù)組的話

我們主要介紹MarkWord

Java對(duì)象創(chuàng)建的流程

根據(jù)鎖狀態(tài)的不同,markword會(huì)復(fù)用自己的空間,分別記錄一些不同的信息。

我們注意到 輕量級(jí)鎖和重量級(jí)鎖狀態(tài)時(shí),會(huì)將分代年齡覆蓋掉,那當(dāng)鎖狀態(tài)解除時(shí),要怎么恢復(fù)呢?

答案是上鎖時(shí),鎖的數(shù)據(jù)中會(huì)保存一份原markword的備份

“Java對(duì)象創(chuàng)建的流程”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!


分享名稱:Java對(duì)象創(chuàng)建的流程
轉(zhuǎn)載源于:http://weahome.cn/article/ggsics.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部