JVM中 對(duì)象的內(nèi)存布局 以及 實(shí)例分析

對(duì)象內(nèi)存結(jié)構(gòu)

在 HotSpot 虛擬機(jī)中吝镣,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為3塊區(qū)域:
① 對(duì)象頭(Header)
② 實(shí)例數(shù)據(jù)(Instance Data)
③ 對(duì)齊填充 (Padding)

對(duì)象頭(Header)

HotSpot 虛擬機(jī)的對(duì)象頭包括兩部分信息:Mark Word 和 類型指針律罢;如果是數(shù)組對(duì)象的話砍聊,還有第三部分(option)信息:數(shù)組長(zhǎng)度
Mark Word
這部分?jǐn)?shù)據(jù)的長(zhǎng)度在32位和64位的虛擬機(jī)(未開啟壓縮指針)中分別為32bit 和64bit砸捏。Mark Word用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),如哈希碼(HashCode)勿璃、GC分代年齡飒货、鎖狀態(tài)標(biāo)志、線程持有的鎖廷臼、偏向線程ID苍在、偏向時(shí)間戳等。
對(duì)象頭信息是與對(duì)象定義的數(shù)據(jù)無關(guān)的額外存儲(chǔ)成本荠商,考慮到虛擬機(jī)的空間效率寂恬,Mark Word被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存存儲(chǔ)盡量多的信息,它會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間莱没。

存儲(chǔ)內(nèi)容 標(biāo)志位 狀態(tài)
對(duì)象哈希碼初肉、對(duì)象分代年齡 01 未鎖定
指向鎖記錄的指針 00 輕量級(jí)鎖定
指向重量級(jí)鎖的指針 10 膨脹(重量級(jí)鎖定)
空,不需要記錄信息 11 GC標(biāo)記
偏向線程ID饰躲、偏向時(shí)間戳牙咏、對(duì)象分代年齡 01 可偏向

??標(biāo)志位“01”就被復(fù)用了臼隔,根據(jù)不同的狀態(tài):“未鎖定” or “可偏向” 來確定“01”存儲(chǔ)所表示的內(nèi)容。

類型指針(Class Pointer)
是對(duì)象指向它的類元數(shù)據(jù)的指針妄壶,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例摔握。

數(shù)組長(zhǎng)度(Length)[option]
如果對(duì)象時(shí)一個(gè)Java數(shù)組,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)丁寄。因?yàn)樘摂M機(jī)可以通過普通Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小氨淌,但是從數(shù)組的元數(shù)據(jù)中無法確定數(shù)組的大小。

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

實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息伊磺,也是在程序代碼中所定義的各種類型的字段內(nèi)容盛正,無論是從父類繼承下來的,還是在子類中定義的屑埋,都需要記錄起來豪筝。

HotSpot虛擬機(jī)默認(rèn)的分配策略為longs/doubles、ints摘能、shorts/chars续崖、bytes/booleans、oops(Ordinary Object Pointers)徊哑,從分配策略中可以看出袜刷,相同寬度的字段總是被分配到一起。在滿足這個(gè)前提條件的情況下莺丑,在父類中定義的變量會(huì)出現(xiàn)在子類之前著蟹。如果CompactFields參數(shù)值為true(默認(rèn)為true),那子類之中較窄的變量也可能會(huì)插入到父類變量的空隙之中梢莽。

對(duì)齊填充 (Padding)

對(duì)齊填充并不是必然存在的萧豆,也沒有特別的含義,它僅僅起著占位符的作用昏名。由于HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍涮雷,換句話說就是對(duì)象的大小必須是8字節(jié)的整數(shù)倍。

對(duì)象占用內(nèi)存大小

上面我們已經(jīng)對(duì)對(duì)象在內(nèi)存的布局有了一點(diǎn)你的了解轻局,接下來我們來看看對(duì)象占用內(nèi)存的大小洪鸭。也就是對(duì)象內(nèi)存結(jié)構(gòu)的每個(gè)部分分別占用多少的內(nèi)存。

對(duì)象頭

普通對(duì)象占用內(nèi)存情況:

? 32 位系統(tǒng) 64 位系統(tǒng)(+UseCompressedOops) 64 位系統(tǒng)(-UseCompressedOops)
Mark Word 4 bytes 8 bytes 8 bytes
Class Pointer 4 bytes 4 bytes 8 bytes
對(duì)象頭 8 bytes 12 bytes 16 bytes

數(shù)組對(duì)象占用內(nèi)存情況:

? 32 位系統(tǒng) 64 位系統(tǒng)(+UseCompressedOops) 64 位系統(tǒng)(-UseCompressedOops)
Mark Word 4 bytes 8 bytes 8 bytes
Class Pointer 4 bytes 4 bytes 8 bytes
Length 4 bytes 4 bytes 4 bytes
對(duì)象頭 12 bytes 16 bytes 20 bytes
實(shí)例數(shù)據(jù)
Type 32 位系統(tǒng) 64 位系統(tǒng)(+UseCompressedOops) 64 位系統(tǒng)(-UseCompressedOops)
double 8 bytes 8 bytes 8 bytes
long 8 bytes 8 bytes 8 bytes
float 4 bytes 4 bytes 4 bytes
int 4 bytes 4 bytes 4 bytes
char 2 bytes 2 bytes 2 bytes
short 2 bytes 2 bytes 2 bytes
byte 1 bytes 1 bytes 1 bytes
boolean 1 bytes 1 bytes 1 bytes
oops(ordinary object pointers) 4 bytes 4 bytes 8 bytes


實(shí)例分析

環(huán)境

系統(tǒng):macOS 10.12.5
JDK:jdk1.8.0_144

涉及JVM參數(shù)

-XX:+UseCompressedOops(JDK 8下默認(rèn)為啟用)

UseCompressedOops
Use 32-bit object references in 64-bit VM. lp64_product means flag is always constant in 32 bit VM
在64位系統(tǒng)中使用32位系統(tǒng)下引用的大小仑扑,也就是說览爵,在64系統(tǒng)下回壓縮普通對(duì)象的指針大小以節(jié)約內(nèi)存占用的大小。



-XX:+CompactFields(JDK 8下默認(rèn)為啟用)

CompactFields
Allocate nonstatic fields in gaps between previous fields
分配一個(gè)非static的字段在前面字段縫隙中镇饮。這么做也是為了提高內(nèi)存的利用率蜓竹。



-XX:FieldsAllocationStyle=1 (JDK 8下默認(rèn)值為‘1’)

FieldsAllocationStyle
0 - type based with oops first, 1 - with oops last, 2 - oops in super and sub classes are together
實(shí)例對(duì)象中有效信息的存儲(chǔ)順序:
0:先放入oops(普通對(duì)象引用指針),然后在放入基本變量類型(順序:longs/doubles、ints俱济、shorts/chars嘶是、bytes/booleans)
1:先放入基本變量類型(順序:longs/doubles、ints蛛碌、shorts/chars聂喇、bytes/booleans),然后放入oops(普通對(duì)象引用指針)
2:oops和基本變量類型交叉存儲(chǔ)

關(guān)于上面的JVM選項(xiàng)含義左医,可以結(jié)合下面的實(shí)例分析授帕,更便于理解同木。

實(shí)例

下文中無特殊說明浮梢,“對(duì)象占用內(nèi)存大小”均指“對(duì)象自身占用內(nèi)存大小”

實(shí)例一

/**
 * ① 將下載的 classmexer.jar 加入當(dāng)前項(xiàng)目的classpath中
 * ② 啟動(dòng)Main是添加啟動(dòng)項(xiàng):-javaagent:${classmexer_path}/classmexer.jar
 * -javaagent:/Users/linling/Documents/software/classmexer-0_03/classmexer.jar
 *
 * ③ JVM 參數(shù):
 * -XX:+UseCompressedOops   (默認(rèn)啟用)
 * -XX:+CompactFields   (默認(rèn)啟用)
 * -XX:FieldsAllocationStyle=1      (默認(rèn)為1)
 */
public class TheObjectMemory {

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * ● 對(duì)象頭:mark word(8 bytes) + class pointer(4 bytes) = 12 bytes
     * 因?yàn)樵贘DK 8 中"UseCompressedOops"選項(xiàng)是默認(rèn)啟用的,因此class pointer只占用了4個(gè)字節(jié)彤路。
     * 同時(shí)秕硝,從屬性'a'在內(nèi)存中的偏移量為12也能說明,對(duì)象頭僅占用了12bytes(屬性a的分配緊跟在對(duì)象頭后)
     *
     * ● 實(shí)例數(shù)據(jù):int (4 bytes)
     *
     * ● 對(duì)齊填充:0 bytes
     * 因?yàn)?對(duì)象頭' + '對(duì)齊填充' 已經(jīng)滿足為8的倍數(shù)洲尊,因此無需填充
     *
     * 對(duì)象占用內(nèi)存大性恫颉:對(duì)象頭(12) + 實(shí)例數(shù)據(jù)(4) + 對(duì)齊填充(0) = 16
     */
    int a;

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

        TheObjectMemory obj = new TheObjectMemory();

        // memoryUsage : 16
        System.out.println("memoryUsage : " + MemoryUtil.memoryUsageOf(obj));

        // a field offset : 12
        System.out.println("a field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("a")));

    }
}

實(shí)例二

/**
 * ① 將下載的 classmexer.jar 加入當(dāng)前項(xiàng)目的classpath中
 * ② 啟動(dòng)Main是添加啟動(dòng)項(xiàng):-javaagent:${classmexer_path}/classmexer.jar
 * -javaagent:/Users/linling/Documents/software/classmexer-0_03/classmexer.jar
 *
 * ③ JVM 參數(shù):
 * -XX:+UseCompressedOops   (默認(rèn)啟用)
 * -XX:+CompactFields   (默認(rèn)啟用)
 * -XX:FieldsAllocationStyle=1      (默認(rèn)為1)
 */
public class TheObjectMemory {

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * ● 對(duì)象頭:mark word(8 bytes) + class pointer(4 bytes) = 12 bytes
     * 因?yàn)樵贘DK 8 中"UseCompressedOops"選項(xiàng)是默認(rèn)啟用的,因此class pointer只占用了4個(gè)字節(jié)坞嘀。
     *
     * ● 實(shí)例數(shù)據(jù):long (8 bytes) + long (8 bytes)
     *
     * ● 對(duì)齊填充:4 bytes
     *
     * 對(duì)象占用內(nèi)存大星ぁ:對(duì)象頭(12) + 實(shí)例數(shù)據(jù)(16) + 對(duì)齊填充(4) = 32
     * 這里請(qǐng)注意,padding的填充不是在最后面的丽涩,即棺滞,不是在實(shí)例數(shù)據(jù)分配完后填充了4個(gè)字
     * 節(jié)。而是在對(duì)象頭分配完后填充了4個(gè)字節(jié)矢渊。這從屬性'a'字段的偏移量為16继准,也能夠說明填充的部分是對(duì)象頭后的4個(gè)字節(jié)空間。
     *
     * 這是為什么了矮男?
     * 是這樣的移必,在64位系統(tǒng)中,CPU一次讀操作可讀取64bit(8 bytes)的數(shù)據(jù)毡鉴。如果崔泵,你在對(duì)象頭分配后就進(jìn)行屬性 long a字
     * 段的分配,也就是說從偏移量為12的地方分配8個(gè)字節(jié)猪瞬,這將導(dǎo)致讀取屬性long a時(shí)需要執(zhí)行兩次讀數(shù)據(jù)操作憎瘸。因?yàn)榈谝淮巫x取
     * 到的數(shù)據(jù)中前4字節(jié)是對(duì)象頭的內(nèi)存,后4字節(jié)是屬性long a的高4位(Java 是大端模式)撑螺,低4位的數(shù)據(jù)則需要通過第二次讀取
     * 操作獲得含思。
     */
    long a;
    long b;
    public static void main(String[] args) throws NoSuchFieldException {

        TheObjectMemory obj = new TheObjectMemory();

        // memoryUsage : 32
        System.out.println("memoryUsage : " + MemoryUtil.memoryUsageOf(obj));

        // a field offset : 16
        System.out.println("a field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("a")));

        // b field offset : 24
        System.out.println("b field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("b")));

    }

}

實(shí)例三

/**
 * ① 將下載的 classmexer.jar 加入當(dāng)前項(xiàng)目的classpath中
 * ② 啟動(dòng)Main是添加啟動(dòng)項(xiàng):-javaagent:${classmexer_path}/classmexer.jar
 * -javaagent:/Users/linling/Documents/software/classmexer-0_03/classmexer.jar
 *
 * ③ JVM 參數(shù):
 * -XX:+UseCompressedOops   (默認(rèn)啟用)
 * -XX:+CompactFields   (默認(rèn)啟用)
 * -XX:FieldsAllocationStyle=1      (默認(rèn)為1)
 */
public class TheObjectMemory {

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * ● 對(duì)象頭:mark word(8 bytes) + class pointer(4 bytes) = 12 bytes
     * 因?yàn)樵贘DK 8 中"UseCompressedOops"選項(xiàng)是默認(rèn)啟用的,因此class pointer只占用了4個(gè)字節(jié)。
     *
     * ● 實(shí)例數(shù)據(jù):long (8 bytes) + int (4 bytes)
     *
     * ● 對(duì)齊填充:0 bytes
     *
     * 對(duì)象占用內(nèi)存大泻恕:對(duì)象頭(12) + 實(shí)例數(shù)據(jù)(12) + 對(duì)齊填充(0) = 24
     *
     * 在前面的理論中饲做,我們說過基本變量類型在內(nèi)存中的存放順序是從大到小的(順序:longs/doubles、ints遏弱、
     * shorts/chars盆均、bytes/booleans)。所以漱逸,按理來說泪姨,屬性int b應(yīng)該被分配到了屬性long a的后面。但是饰抒,從屬性位置
     * 偏移量的結(jié)果來看肮砾,我們卻發(fā)現(xiàn)屬性int b被分配到了屬性long a的前面,這是為什么了袋坑?
     * 是這樣的仗处,因?yàn)镴VM啟用了'CompactFields'選項(xiàng),該選項(xiàng)運(yùn)行分配的非靜態(tài)(non-static)字段被插入到前面字段的空隙
     * 中枣宫,以提供內(nèi)存的利用率婆誓。
     * 從前面的實(shí)例中,我們已經(jīng)知道也颤,對(duì)象頭占用了12個(gè)字節(jié)洋幻,并且再次之后分配的long類型字段不會(huì)緊跟在對(duì)象頭后面分配,而是
     * 在新一個(gè)8字節(jié)偏移量位置處開始分配翅娶,因此對(duì)象頭和屬性long a直接存在了4字節(jié)的空隙文留,而這個(gè)4字節(jié)空隙的大小符合(即,
     * 大小足以用于)屬性int b的內(nèi)存分配故觅。所以厂庇,屬性int b就被插入到了對(duì)象頭與屬性long a之間了。
     */
    long a;
    int b;
    public static void main(String[] args) throws NoSuchFieldException {

        TheObjectMemory obj = new TheObjectMemory();

        // memoryUsage : 24
        System.out.println("memoryUsage : " + MemoryUtil.memoryUsageOf(obj));

        // a field offset : 16
        System.out.println("a field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("a")));

        // b field offset : 12
        System.out.println("b field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("b")));
    }

}

實(shí)例四

/**
 * ① 將下載的 classmexer.jar 加入當(dāng)前項(xiàng)目的classpath中
 * ② 啟動(dòng)Main是添加啟動(dòng)項(xiàng):-javaagent:${classmexer_path}/classmexer.jar
 * -javaagent:/Users/linling/Documents/software/classmexer-0_03/classmexer.jar
 *
 * ③ JVM 參數(shù):
 * -XX:+UseCompressedOops   (默認(rèn)啟用)
 * -XX:+CompactFields   (默認(rèn)啟用)
 * -XX:FieldsAllocationStyle=1      (默認(rèn)為1)
 */
public class TheObjectMemory {

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * ● 對(duì)象頭:mark word(8 bytes) + class pointer(4 bytes) = 12 bytes
     * 因?yàn)樵贘DK 8 中"UseCompressedOops"選項(xiàng)是默認(rèn)啟用的输吏,因此class pointer只占用了4個(gè)字節(jié)权旷。
     *
     * ● 實(shí)例數(shù)據(jù):long (8 bytes) + int (4 bytes) + oops (4 bytes)
     *
     * ● 對(duì)齊填充:4 bytes
     *
     * 對(duì)象占用內(nèi)存大小:對(duì)象頭(12) + 實(shí)例數(shù)據(jù)(16) + 對(duì)齊填充(4) = 32
     *
     * 從屬性 int a贯溅、long b拄氯,以及對(duì)象引用 str 的偏移量可以發(fā)現(xiàn),對(duì)象引用是在基本變量分配完后才進(jìn)行的分配的它浅。這是通過
     * JVM選項(xiàng)'FieldsAllocationStyle=1'決定的译柏,F(xiàn)ieldsAllocationStyle的值為1,說明:先放入基本變量類型(順序:
     * longs/doubles姐霍、ints鄙麦、shorts/chars典唇、bytes/booleans),然后放入oops(普通對(duì)象引用指針)
     *
     */
    int a;
    long b;
    String str;
    public static void main(String[] args) throws NoSuchFieldException {

        TheObjectMemory obj = new TheObjectMemory();

        // memoryUsage : 24
        System.out.println("memoryUsage : " + MemoryUtil.memoryUsageOf(obj));

        // a field offset : 12
        System.out.println("a field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("a")));

        // str field offset : 16
        System.out.println("b field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("b")));

        // str field offset : 24
        System.out.println("str field offset : " + 
              UNSAFE.objectFieldOffset(TheObjectMemory.class.getDeclaredField("str")));
    }

}

實(shí)例五

/**
 * ① 將下載的 classmexer.jar 加入當(dāng)前項(xiàng)目的classpath中
 * ② 啟動(dòng)Main是添加啟動(dòng)項(xiàng):-javaagent:${classmexer_path}/classmexer.jar
 * -javaagent:/Users/linling/Documents/software/classmexer-0_03/classmexer.jar
 *
 * ③ JVM 參數(shù):
 * -XX:+UseCompressedOops   (默認(rèn)啟用)
 * -XX:+CompactFields   (默認(rèn)啟用)
 * -XX:FieldsAllocationStyle=1      (默認(rèn)為1)
 */
public class TheObjectMemory {

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

     /**
     * memoryUsageOf方法僅計(jì)算了對(duì)象本身的大小胯府,并未包含引用對(duì)象的內(nèi)存大薪橄巍(注意,memoryUsageOf方法計(jì)算的是引用指針
     * 的對(duì)象骂因,而非引用對(duì)象占用的內(nèi)存大醒卓А)。
     * deepMemoryUsageOf方法則會(huì)將引用對(duì)象占用的內(nèi)存大小也計(jì)算進(jìn)來寒波。
     *
     * 注意乘盼,deepMemoryUsageOf(Object obj)默認(rèn)只會(huì)包含non-public的引用對(duì)象的大
     * 小。如果你想將public引用對(duì)象的大小也計(jì)算在內(nèi)俄烁,可通過deepMemoryUsageOf重載方法
     * deepMemoryUsageOf(Object obj, VisibilityFilter referenceFilter)绸栅,VisibilityFilter參數(shù)傳入
     * 'VisibilityFilter.ALL'來實(shí)現(xiàn)。
     */
    static class TheInnerObject {
        int innerA;
    }
    TheInnerObject innerObject = new TheInnerObject();
    public static void main(String[] args) throws NoSuchFieldException {

        TheObjectMemory obj = new TheObjectMemory();

        // TheObjectMemory memoryUsage : 16
        System.out.println("TheObjectMemory memoryUsage : " + MemoryUtil.memoryUsageOf(obj));

        // TheInnerObject memoryUsage : 16
        TheInnerObject innerObj = new TheInnerObject();
        System.out.println("TheInnerObject memoryUsage : " + MemoryUtil.memoryUsageOf(innerObj));

        // TheObjectMemory deepMemoryUsageOf : 32
        System.out.println("TheObjectMemory deepMemoryUsageOf : " + 
              MemoryUtil.deepMemoryUsageOf(obj));

    }

}

實(shí)例六

/**
 * ① 將下載的 classmexer.jar 加入當(dāng)前項(xiàng)目的classpath中
 * ② 啟動(dòng)Main是添加啟動(dòng)項(xiàng):-javaagent:${classmexer_path}/classmexer.jar
 * -javaagent:/Users/linling/Documents/software/classmexer-0_03/classmexer.jar
 *
 * ③ JVM 參數(shù):
 * -XX:+UseCompressedOops   (默認(rèn)啟用)
 * -XX:+CompactFields   (默認(rèn)啟用)
 * -XX:FieldsAllocationStyle=1      (默認(rèn)為1)
 */
public class TheObjectMemory {

    private static Unsafe UNSAFE;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

     /**
     * 數(shù)組對(duì)象自身占用的內(nèi)存大小 = 對(duì)象頭 + 數(shù)組長(zhǎng)度 * 元素引用指針/基本數(shù)據(jù)類型大小 + 對(duì)齊填充
     *
     * ● 對(duì)象頭:mark word(8 bytes) + class pointer(4 bytes) + length(4 bytes) = 16 bytes
     * 因?yàn)樵贘DK 8 中"UseCompressedOops"選項(xiàng)是默認(rèn)啟用的猴娩,因此class pointer只占用了4個(gè)字節(jié)阴幌。
     *
     * ● 實(shí)例數(shù)據(jù):數(shù)組長(zhǎng)度(1) * 對(duì)象引用指針(4 bytes) = 4 bytes
     *
     * ● 對(duì)齊填充:4 bytes
     *
     * 對(duì)象占用內(nèi)存大猩撞:對(duì)象頭(16) + 實(shí)例數(shù)據(jù)(4) + 對(duì)齊填充(4) = 24
     *
     * deepMemoryUsageOf = array memoryUsage + array_length(數(shù)組長(zhǎng)度) * item_deepMemoryUsage (元素占用
     * 的全部?jī)?nèi)存)
     *
     * 注意卷中,這里的數(shù)組是一個(gè)對(duì)象數(shù)組,因此memoryUsage中計(jì)算的是對(duì)象引用指針的大小渊抽。如果是一個(gè)基本數(shù)據(jù)類型的數(shù)組蟆豫,如,
     * int[]懒闷,則十减,memoryUsage計(jì)算的就是基本數(shù)據(jù)類型的大小了。也就是說愤估,如果是基本數(shù)據(jù)類型的數(shù)組的話帮辟,memoryUsage
     * 的值是等于deepMemoryUsageOf的值的。
     *
     */
    int a;
    String str = "hello";
    public static void main(String[] args) throws NoSuchFieldException {
        TheObjectMemory[] objArray = new TheObjectMemory[1];
        TheObjectMemory obj = new TheObjectMemory();
        objArray[0] = obj;

        // memoryUsage : 24
        System.out.println("objArray memoryUsage : " + MemoryUtil.memoryUsageOf(objArray));

        // deepMemoryUsageOf : 104
        System.out.println("objArray deepMemoryUsageOf : " + MemoryUtil.deepMemoryUsageOf(objArray));

        // obj memoryUsage : 24
        System.out.println("obj memoryUsage : " + MemoryUtil.memoryUsageOf(obj));
        // obj deepMemoryUsageOf : 80
        System.out.println("obj deepMemoryUsageOf : " + MemoryUtil.deepMemoryUsageOf(obj));

        // first item offset(數(shù)組第一個(gè)元素的內(nèi)存地址偏移量) : 16
        System.out.println("first item offset : " + UNSAFE.arrayBaseOffset(objArray.getClass()));
    }

}


后記

如果文章有錯(cuò)不吝指教 :)

參考

《深入理解Java虛擬機(jī)》
classmexer
object_memory_usage
jvm-options

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玩焰,一起剝皮案震驚了整個(gè)濱河市由驹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昔园,老刑警劉巖蔓榄,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異默刚,居然都是意外死亡甥郑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門荤西,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澜搅,“玉大人伍俘,你說我怎么就攤上這事∶闾桑” “怎么了养篓?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)赂蕴。 經(jīng)常有香客問我柳弄,道長(zhǎng),這世上最難降的妖魔是什么概说? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任碧注,我火速辦了婚禮,結(jié)果婚禮上糖赔,老公的妹妹穿的比我還像新娘萍丐。我一直安慰自己,他們只是感情好放典,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布逝变。 她就那樣靜靜地躺著,像睡著了一般奋构。 火紅的嫁衣襯著肌膚如雪壳影。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天弥臼,我揣著相機(jī)與錄音宴咧,去河邊找鬼。 笑死径缅,一個(gè)胖子當(dāng)著我的面吹牛掺栅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纳猪,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼氧卧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了氏堤?” 一聲冷哼從身側(cè)響起沙绝,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丽猬,沒想到半個(gè)月后宿饱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脚祟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年谬以,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片由桌。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡为黎,死狀恐怖邮丰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铭乾,我是刑警寧澤剪廉,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站炕檩,受9級(jí)特大地震影響斗蒋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笛质,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一泉沾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妇押,春花似錦跷究、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肩杈,卻和暖如春柴我,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锋恬。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工屯换, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人与学。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嘉抓,于是被迫代替她去往敵國(guó)和親索守。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容