對(duì)象創(chuàng)建問題

1. 請(qǐng)解釋一下對(duì)象的創(chuàng)建過程?(半初始化)

對(duì)象創(chuàng)建過程

2.對(duì)象在內(nèi)存中的存儲(chǔ)布局?(對(duì)象與數(shù)組的存儲(chǔ)不同)

對(duì)象與數(shù)組的存儲(chǔ)不同

對(duì)象在內(nèi)存中的構(gòu)成布局
  • markword:鎖狀態(tài)、分代年齡、hashcode、偏向線程ID、偏向時(shí)間戳等信息。在32位系統(tǒng)占4字節(jié),在64位系統(tǒng)中占8字節(jié)。

  • 類型指針class pointer:對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例, 32位系統(tǒng)占4字節(jié),在64位系統(tǒng)中占8字節(jié)。

  • 實(shí)例數(shù)據(jù)Instance data:是對(duì)象存儲(chǔ)的真正的有效信息,存儲(chǔ)著自身定義的和從父類繼承下來的實(shí)例字段。字段的存儲(chǔ)熟悉怒會(huì)受虛擬機(jī)的分配策略和字段在java源碼中

  • 數(shù)組長(zhǎng)度length:如果是數(shù)組對(duì)象,還有一個(gè)保存數(shù)組長(zhǎng)度的空間,占4個(gè)字節(jié);

  • 對(duì)齊padding,由于虛擬機(jī)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍,填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對(duì)齊。

3.對(duì)象頭具體包括什么?(markword classpointer)synchronized鎖信息

markword與類型指針都是屬于對(duì)象頭

對(duì)象頭存儲(chǔ)信息

一個(gè)剛剛 new 出來的對(duì)象,如果開始上鎖 (synchronized),它的一個(gè)升級(jí)過程是:

new -> 偏向鎖 -> 自旋鎖(無鎖、lock-free、輕量級(jí)鎖) -> 重量級(jí)鎖。這些信息都記錄在 markword 里面。
對(duì)象實(shí)際數(shù)據(jù)
對(duì)象實(shí)際數(shù)據(jù)包括了對(duì)象的所有成員變量,其大小由各個(gè)成員變量的大小決定,比如:byte和boolean是1個(gè)字節(jié),short和char是2個(gè)字節(jié),int和float是4個(gè)字節(jié),long和double是8個(gè)字節(jié),reference是4個(gè)字節(jié)(64位系統(tǒng)中是8個(gè)字節(jié))。

Primitive Type Memory Required(bytes)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8

對(duì)于reference類型來說,在32位系統(tǒng)上占用4bytes, 在64位系統(tǒng)上占用8bytes。

對(duì)象頭占用空間大小

這里說明一下32位系統(tǒng)和64位系統(tǒng)中對(duì)象所占用內(nèi)存空間的大?。?/p>

數(shù)據(jù) 32位操作系統(tǒng) 64位操作系統(tǒng) 64位開啟指針壓縮
MarkWord 4 8 -
Class Pointer 4 8 4

指針壓縮

64位JVM消耗的內(nèi)存會(huì)比32位的要多大約1.5倍,這是因?yàn)閷?duì)象指針在64位JVM下有更寬的尋址。對(duì)于那些將要從32位平臺(tái)移植到64位的應(yīng)用來說,平白無辜多了1/2的內(nèi)存占用,這是開發(fā)者不愿意看到的。

從JDK 1.6 update14開始,64位的JVM正式支持了 -XX:+UseCompressedOops 這個(gè)可以壓縮指針,起到節(jié)約內(nèi)存占用的新參數(shù)。

會(huì)壓縮的對(duì)象

  • 每個(gè)Class的屬性指針(靜態(tài)成員變量);
  • 每個(gè)對(duì)象的屬性指針;
  • 普通對(duì)象數(shù)組的每個(gè)元素指針。

` 當(dāng)然,壓縮也不是所有的指針都會(huì)壓縮,對(duì)一些特殊類型的指針,JVM是不會(huì)優(yōu)化的,例如指向PermGen(永久代)的Class對(duì)象指針、本地變量、堆棧元素、入?yún)?、返回值和NULL指針不會(huì)被壓縮。

啟用指針壓縮

在Java程序啟動(dòng)時(shí)增加JVM參數(shù):-XX:+UseCompressedOops來啟用。

注意:32位HotSpot VM是不支持UseCompressedOops參數(shù)的,只有64位HotSpot VM才支持

本文中使用的是JDK 1.8,默認(rèn)該參數(shù)就是開啟的。

4.對(duì)象怎么定位?(直接 間接)

兩種方式:句柄方式 、 直接指針

當(dāng)在堆內(nèi)存中創(chuàng)建對(duì)象之后,java程序需要refrence數(shù)據(jù)來操作對(duì)象。由于reference類型在java虛擬機(jī)規(guī)范中只規(guī)定了指向?qū)ο蟮囊?并沒有規(guī)定這個(gè)引用以如何的方式去定位、訪問堆中的對(duì)象具體位置.

其中Reference是java中的引用類,它主要是對(duì)普通對(duì)象進(jìn)行包裝,從而在JVM在垃圾回收時(shí),按照引用類型的不同,在回收時(shí)采用不同的邏輯。

4.1 句柄

使用句柄方式訪問兌對(duì)象時(shí),需要在堆中劃出一部分內(nèi)存作為句柄池,句柄池中存放各個(gè)對(duì)象的句柄,句柄包含了:

對(duì)象實(shí)例數(shù)據(jù)的指針
對(duì)象類型數(shù)據(jù)的指針
而reference中存儲(chǔ)的則是對(duì)象的句柄地址。

對(duì)象實(shí)例數(shù)據(jù):對(duì)象實(shí)例字段的數(shù)據(jù)
對(duì)象類型數(shù)據(jù):對(duì)象的類型、父類實(shí)現(xiàn)的接口、方法等

image

4.2 直接指針

使用直接方式訪問對(duì)象時(shí),堆中對(duì)象存放的是對(duì)象的實(shí)例數(shù)據(jù)和指向?qū)ο箢愋蛿?shù)據(jù)的指針,reference則存儲(chǔ)的是對(duì)象地址。


image

4.3 兩種方式的對(duì)比

image

2. 加問 DCL 與 volatile 問題?(指令重排)

為了理解什么是 DCL (雙檢鎖/雙重校驗(yàn)鎖(DCL,即 double-checked-locking)),我們先回顧一下 單例模式(Singleton Pattern)。

  • 單例類只能有一個(gè)實(shí)例。
  • 單例類必須直接創(chuàng)建直接的唯一實(shí)例。
  • 單例類必須給所有其他對(duì)象提供這一實(shí)例。

參考代碼1

/**
 *  餓漢模式
 *  類加載到內(nèi)存后,就實(shí)例化一個(gè)單例。JVM保證線程安全
 *  簡(jiǎn)單使用,推薦使用
 *  唯一缺點(diǎn):不管用到與否,類裝載時(shí)就完成實(shí)例化
 *  Class.forName("")
 *     (話說你不用的,你裝載它干啥)
 */
public class Mgr01 {
    // 創(chuàng)建 Mgr01 的一個(gè)對(duì)象
    private static final Mgr01 INSTANCE = new Mgr01();

    //讓構(gòu)造函數(shù)為 private,這樣該類就不會(huì)被實(shí)例化
    private Mgr01(){

    }

    // 獲取唯一可用的對(duì)象
    public static Mgr01 getInstance(){
        return  INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }

參考代碼1,這種寫法有人會(huì)說 INSTANCE還沒用就直接 new 出來了,假如說創(chuàng)建的過程特別浪費(fèi)資源,能不能夠等我想用的時(shí)候再初始化出來。

參考代碼2:

/**
 * 雖然達(dá)到了按需初始化的目的,但卻帶來了線程不安全
 */
public class Mgr02 {
    private static Mgr02 INSTANCE;

    private Mgr02() {

    }

    public static Mgr02 getInstance() {
        if (INSTANCE == null) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            INSTANCE = new Mgr02();
        }
        return  INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i< 100; i++) {
            new Thread(() ->
                    System.out.println(Mgr02.getInstance().hashCode())
            ).start();
        }
    }
}

還有人接著說,參考代碼2,線程不安全,多線程訪問情況下有可能會(huì) new 出多個(gè)對(duì)象出來。自然而然我們想到加鎖來解決.

參考代碼3

/**
 * 增加synchronized,線程安全
 */
public class Mgr03 {
    private static Mgr03 INSTANCE;

    private Mgr03() {

    }

    public static synchronized Mgr03 getInstance() {
        if (INSTANCE == null) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            INSTANCE = new Mgr03();
        }
        return  INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i< 100; i++) {
            new Thread(() ->
                    System.out.println(Mgr03.getInstance().hashCode())
            ).start();
        }
    }
}

可是有的人還會(huì)說,你上來二話不說整個(gè)方法全上鎖,鎖的粒度是不是太粗了。于是我們換個(gè)寫法。

參考代碼4

public class Mgr04 {
    private static Mgr04 INSTANCE;

    private Mgr04() {

    }

    public static Mgr04 getInstance() {
        // 業(yè)務(wù)代碼
        if (INSTANCE == null) {
            // 妄圖通過減少同步代碼塊的方式提高效率,然后不可行
            synchronized (Mgr04.class) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                INSTANCE = new Mgr04();
            }
        }
        return  INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i< 100; i++) {
            new Thread(() ->
                    System.out.println(Mgr04.getInstance().hashCode())
            ).start();
        }
    }
}

這個(gè)版本在多線程訪問情況下,是線程不安全的。于是誕生了 “DCL” 寫法。

參考代碼5

public class Mgr05 {
    private static volatile Mgr05 INSTANCE;

    private Mgr05() {

    }

    public static Mgr05 getInstance() {
        // 業(yè)務(wù)代碼
        if (INSTANCE == null) { // Double Check Lock
            // 雙重檢查
            synchronized (Mgr05.class) {
                if (INSTANCE == null) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    INSTANCE = new Mgr05();
                }
            }
        }
        return  INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i< 100; i++) {
            new Thread(() ->
                    System.out.println(Mgr05.getInstance().hashCode())
            ).start();
        }
    }
}

volatile 主要有兩個(gè)作用:

線程可見性
禁止指令重排序(上下都要加內(nèi)存屏障)

那么到底需不需加 volatile 關(guān)鍵字,我們來分析下:

當(dāng)?shù)谝粋€(gè)線程來的時(shí)候,判斷它為空,開始對(duì)它進(jìn)行初始化(new)。當(dāng) new 一半的時(shí)候,只拿到了默認(rèn)值,還沒獲取初始化值。



這個(gè)時(shí)候下面兩條指令有可能會(huì)發(fā)生 指令重排序 ,這時(shí)候就會(huì)先建立關(guān)聯(lián),再調(diào)用構(gòu)造方法賦予初始值。目前 t 就執(zhí)行了 半初始化 的這個(gè)狀態(tài)對(duì)象


當(dāng) t 指向半初始化狀態(tài)對(duì)象的時(shí)候,正好這個(gè)時(shí)候第二個(gè)線程來了,當(dāng)前 t指向了半初始化狀態(tài)的對(duì)象, 肯定不為空。那就直接用了,那就用半初始化狀態(tài)的這個(gè)對(duì)象,就會(huì)發(fā)生不可預(yù)知的錯(cuò)誤。

6.對(duì)象怎么分配?(棧上-線程本地-Eden-Old)

7.Object o = new Object() 在內(nèi)存中占用多少字節(jié)?

對(duì)象的內(nèi)存以字節(jié)為單位,且必須是8的倍數(shù),它由3部分組成:

對(duì)象頭 + 實(shí)例數(shù)據(jù) + 對(duì)齊內(nèi)存
對(duì)象頭= Mark word + 類型指針

http://www.itdecent.cn/p/91e398d5d17c

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容