JVM調優基礎篇-java對象大小計算

背景簡化:最近由於項目需要,需要計算一下對象的大小,防止放開灰度後導致服務期頻繁GC

讀完這篇文章可以獲得什麼?

  • 對象的內存佈局
  • 指針壓縮的原理
  • 預估對象的大小
  • 對象是否只能在堆上分配

基礎

1、對象的內存佈局

一個Java對象在內存中包括對象頭、實例數據和補齊填充3個部分
對象的內存佈局
由於本文主要是講述對象的大小計算,所以不會詳細講解每個部分的作用,有興趣可以上網搜索一些相關文章閱讀。

對象頭

所有對象都會有的部分:
Mark Word
Mark Word 用於存儲對象自身的運行時數據
如哈希碼(HashCode)、GC 分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID 、偏向時間戳等
這部分數據的長度在32 位和64 位的虛擬機中分別爲32 bit 和64 bit 
Klass Pointer
用來指向對象對應的Class對象(其對應的元數據對象)的內存地址。
在32位系統佔4字節,在64位系統中佔8字節;
64位機中開啓指針壓縮佔4字節

注意是klass 而不是class許多文章都是class pointer可參考HotSpotGlossary

數組對象存在的部分:
Length
如果是數組對象,還有一個保存數組長度的空間,佔4個字節
Padding
如果是數組對象且未開啓指針壓縮則還會存在一個padding用來對齊

這個部分很多文章都省略了但確實存在

Instance Data

  對象真正存儲的有效的信息,程序代碼中定義的各種的數據的類型
  如果有繼承的關係,還有繼承父類的字段。
  分配策略(參數FiedsAllocationStyle)影響java中定義的順序,對相同寬度的字段總是被分配到一起,在這種情況下,父類定義的變量會出現在子類之前。CompactFields 爲true (默認爲true) 子類中較窄的變量也可能插入到父類變量中。 

HostSpot 的默認分配策略爲

  • longs/doubles
  • ints
  • shorts/chars
  • bytes/booleans
  • oops(Ordinary Object Pointers)
數據類型分爲基本數據類型和引用數據類型
基本數據類型
Type Bytes
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8
引用數據類型
在32位系統佔4字節,在64位系統中佔8字節

Padding

由於 HostSpot VM 的自動內存管理系統要求對象的起始地址必須是8字節的整數倍
換句話說對象的大小必須爲8字節的整數倍,要是實例數據沒有對齊,則需要進行對齊填充來補全
以8字節對齊還是16字節對齊可以配置

這部分沒有特殊意義填充0值

2、指針壓縮

64位的JVM支持 -XX:+UseCompressedOops  來開啓指針壓縮功能 1.6 後默認開啓
啓用CompressOops後會壓縮的對象:
    1、每個Class的屬性指針(靜態成員變量)
    2、每個對象的屬性指針
    3、普通對象數組的每個元素指針
當然,壓縮也不是所有的指針都會壓縮,對一些特殊類型的指針,JVM是不會優化的
例如指向PermGen的Class對象指針、本地變量、堆棧元素、入參、返回值和NULL指針不會被壓縮。

指針壓縮的實現原理

前提條件

java對象默認按8字節對齊
假設內存中只有三個對象 t1 = 16字節 t2 = 32字節 t3 = 24字節
再假設分配內存是從0開始分配 則三個對象的內存地址爲

  • 第一個內存地址 0X00000
  • 第二個內存地址 0X10000
  • 第三個內存地址 0X30000
    這時候再想一下以8字節分配有什麼特點?後三位永遠都是0
結果

實現原理爲存儲的時候後三位0抹除 0X00 0X10 0X30``````使用的時候後三位補0
實際就是一個編碼和解碼的過程,針對指針壓縮也有一些優化,如零基壓縮,由於本文是希望儘可能簡單的將壓縮的實現原理,所以不再這篇文章贅述,感興趣的可以搜一些相關文章查看

問題

一個oop所能表示的最大的內存空間是多少? 2的35次方 = 32G
爲什麼是 35呢? 開啓指針壓縮後 一個oop的大小是4字節 = 32 位 再加上取出後 後三位會補充0 所以是 32+3 =35
怎麼樣擴大oop的最大的內存空間呢?
改爲16 /32 或者更大的 字節對齊 但是這樣做的會導致空間的浪費沒有必要

對象大小的計算

有了上邊的基礎,我們再來進行對象大小的計算

1、沒有實例數據的對象

public class EmptyTuan {
    
    public static void main(String[] args) {
        //使用jol計算對象的大小(單位爲字節):
        System.out.println(ClassLayout.parseClass(EmptyTuan.class).toPrintable());

        //使用jol查看對象的內存佈局:
        System.out.println(ClassLayout.parseInstance(new EmptyTuan()).toPrintable());
    }
}

開啓指針壓縮

對象內存分佈
org.learn.code.jvm.classsize.EmptyTuan object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

16 = 8 (mark word) + 4 (klass Pointer) + 0 (Instance Data) + 4 (padding)
由於按8字節對齊所以浪費了4個字節

關閉指針壓縮

-XX:-UseCompressedOops

對象內存分佈
org.learn.code.jvm.classsize.EmptyTuan object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           28 e0 15 12 (00101000 11100000 00010101 00010010) (303423528)
     12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

通過對象的內存佈局可以看到
16 = 8(mark word)+ 8(klass Pointer) + 0 (Instance Data) + 0 (padding)
因爲關閉指針壓縮後OOP的大小爲8字節 並且正好是8的整數倍,所以不用填充,沒有空間浪費

2、有實例數據的對象

public class FullTuan {
    int a = 10;
    long b = 20L;

    public static void main(String[] args) {
        //使用jol計算對象的大小(單位爲字節):
        System.out.println(ClassLayout.parseClass(FullTuan.class).toPrintable());
        //使用jol查看對象的內存佈局:
        System.out.println(ClassLayout.parseInstance(new FullTuan()).toPrintable());
    }
}

開啓指針壓縮

先來計算一下 ?
8(Mark Word) + 4(Klass Pointer) + 4(Instance Data) + 8(Instance Data) =24

對象內存分佈
org.learn.code.jvm.classsize.FullTuan object internals:
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
     0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
    12     4    int FullTuan.a                                10
    16     8   long FullTuan.b                                20
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

關閉指針壓縮

先來計算一下
8(Mark Word) + 8(Klass Pointer) + 4(Instance Data) + 8(Instance Data)+ 4 (Padding) = 32

對象內存分佈
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           28 e0 3c 10 (00101000 11100000 00111100 00010000) (272425000)
     12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     8   long FullTuan.b                                20
     24     4    int FullTuan.a                                10
     28     4        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

3、數組對象

前邊說的是普通對象,記得在基礎知識中提到過數組對象的對象頭會多出來兩部分數據

public class ArrayTuan {
    /**
     * 數組對象
     */
    private static int[] array = {1, 2, 3,4};
    public static void main(String[] args) {
        //使用jol查看對象的內存佈局:
        System.out.println(ClassLayout.parseInstance(array).toPrintable());
    }
}

開啓指針壓縮

對象內存分佈
[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
     12     4        (object header)                           04 00 00 00 (00000100 00000000 00000000 00000000) (4)
     16    16    int [I.<elements>                             N/A
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

對象頭中又多出來4字節

關閉指針壓縮

對象內存分佈
[I object internals:
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
     0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     8     4        (object header)                           68 0b dc 09 (01101000 00001011 11011100 00001001) (165415784)
    12     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
    16     4        (object header)                           04 00 00 00 (00000100 00000000 00000000 00000000) (4)
    20     4        (alignment/padding gap)                  
    24    16    int [I.<elements>                             N/A
Instance size: 40 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

4 bytes internal證明是在對象頭內部填充了4字節

4、繼承父類的對象

public class ParentTuan {
    
    private int a = 0;

    private long b = 10;
}
public class SonTuan extends ParentTuan {

    private String s = "123";

    public static void main(String[] args) {
        //使用jol計算對象的大小(單位爲字節):
        System.out.println(ClassLayout.parseClass(SonTuan.class).toPrintable());

        //使用jol查看對象的內存佈局:
        System.out.println(ClassLayout.parseInstance(new SonTuan()).toPrintable());
    }
}

驗證一下私有屬性是否可以被繼承過去

開啓指針壓縮

對象內存分佈
org.learn.code.jvm.classsize.SonTuan object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4                int ParentTuan.a                              0
     16     8               long ParentTuan.b                              10
     24     4   java.lang.String SonTuan.s                                 (object)
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

看完內存佈局,有沒有好奇他們的順序是怎麼排的呢?
-XX:FieldsAllocationStyle=1(JDK 8下默認值爲‘1’)
改變這個的參數可以改變實例對象中有效信息的存儲順序:
0:先放入oops(普通對象引用指針),然後在放入基本變量類型(順序:longs/doubles、ints、shorts/chars、bytes/booleans)
1:先放入基本變量類型(順序:longs/doubles、ints、shorts/chars、bytes/booleans),然後放入oops(普通對象引用指針)
2:oops和基本變量類型交叉存儲

現在來把String放到前邊

-XX:FieldsAllocationStyle=0

org.learn.code.jvm.classsize.SonTuan object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4                int ParentTuan.a                              0
     16     8               long ParentTuan.b                              10
     24     4   java.lang.String SonTuan.s                                 (object)
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

沒有變化 難道是因爲父類的屬性是 private 原因嗎?修改一下 在進行驗證

public class ParentTuan {

    int a = 0;

    private long b = 10;
}

再次打印內存佈局

org.learn.code.jvm.classsize.SonTuan object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4                int ParentTuan.a                              0
     16     8               long ParentTuan.b                              10
     24     4   java.lang.String SonTuan.s                                 (object)
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

還是沒有變化 那是不是隻能修改子類的順序呢?先修改子類的結構如下

public class SonTuan extends ParentTuan {

    private String s = "123";

    int c = 10;

    public static void main(String[] args) {
        //使用jol計算對象的大小(單位爲字節):
//        System.out.println(ClassLayout.parseClass(SonTuan.class).toPrintable());

        //使用jol查看對象的內存佈局:
        System.out.println(ClassLayout.parseInstance(new SonTuan()).toPrintable());
    }
}

再次打印對象佈局

org.learn.code.jvm.classsize.SonTuan object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4                int ParentTuan.a                              0
     16     8               long ParentTuan.b                              10
     24     4   java.lang.String SonTuan.s                                 (object)
     28     4                int SonTuan.c                                 10
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

發現是我們期望的結果,所以這個參數是修改不了父類和子類屬性的順序的

關閉指針壓縮

對象內存分佈
org.learn.code.jvm.classsize.SonTuan object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           20 e4 d1 11 (00100000 11100100 11010001 00010001) (298968096)
     12     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     8               long ParentTuan.b                              10
     24     4                int ParentTuan.a                              0
     28     4                    (alignment/padding gap)                  
     32     8   java.lang.String SonTuan.s                                 (object)
Instance size: 40 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

經過前邊的事例計算,相信大家已經對計算對象大小的方法有所掌握,但是現在爲止,我們還沒有在類中加入過靜態變量,下面就讓我們看一下有靜態變量的對象的大小是怎麼計算的。

5、有靜態變量的對象

public class StaticTuan {

    private int a = 1;

    private long b = 2;

    private String s = null;

    private static EmptyTuan emptyTuan = null;

    public static void main(String[] args) {
        //使用jol計算對象的大小(單位爲字節):
        System.out.println(ClassLayout.parseClass(StaticTuan.class).toPrintable());

        //使用jol查看對象的內存佈局:
//        System.out.println(ClassLayout.parseInstance(new StaticTuan()).toPrintable());
    }
}

開啓指針壓縮

靜態屬性的大小是不計算在對象裏面的

對象內存分佈
org.learn.code.jvm.classsize.StaticTuan object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int StaticTuan.a                              1
     16     8               long StaticTuan.b                              2
     24     4   java.lang.String StaticTuan.s                              null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

關閉指針壓縮

對象內存分佈
org.learn.code.jvm.classsize.StaticTuan object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           80 f0 d8 0c (10000000 11110000 11011000 00001100) (215543936)
     12     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
     16     8               long StaticTuan.b                              2
     24     4                int StaticTuan.a                              1
     28     4                    (alignment/padding gap)                  
     32     8   java.lang.String StaticTuan.s                              null
Instance size: 40 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

是否所有對象的分配都在堆上進行

看完上邊內容,我們是不是就可以愉快的去預估項目中內存的佔用了呢?
我先寫個例子:

public class MemoryAllocateTuan {

    public static void main(String[] args) {
        
        System.out.println("開始執行");
        for (int i = 0; i < 1000; i++) {
            new MemoryAllocateTuan();
        }
        System.out.println("執行結束");
    }
}

如上對象是沒有任何屬性的,所以在開啓指針壓縮的情況下 大小=16字節
現在我們限制對內存大小爲 5M 運行 1000 次 16*1000/1024=15M 應該報內存溢出 那我們執行一下看是不是我們期望的結果

開始執行
執行結束 time =0

Process finished with exit code 0

並沒有出現內存溢出問題 再次修改代碼 使我們可以dump內存

public class MemoryAllocateTuan {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        try {

            System.out.println("開始執行");
            for (int i = 0; i < 1000; i++) {
                new MemoryAllocateTuan();
            }
            System.in.read();
        } finally {
            System.out.println("執行結束 time =" + (start - System.currentTimeMillis()));
        }
    }

通過 jconsole 查看內存分配
堆內存大小變化
從上圖可以看到,堆基本沒有分配內存。那對象是在哪產生的呢?
在《深入理解Java虛擬機中》關於Java堆內存有這樣一段描述:
但是,隨着JIT編譯期的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化,所有的對象都分配到堆上也漸漸變得不那麼絕對了。

內存逃逸分析

逃逸分析(Escape Analysis)是目前Java虛擬機中比較前沿的優化技術。這是一種可以有效減少Java 程序中同步負載和內存堆分配壓力的跨函數全局數據流分析算法。

通過逃逸分析,Java Hotspot編譯器能夠分析出一個新的對象的引用的使用範圍從而決定是否要將這個對象分配到堆上。

逃逸分析的基本行爲就是分析對象動態作用域:當一個對象在方法中被定義後,它可能被外部方法所引用,例如作爲調用參數傳遞到其他地方中,稱爲方法逃逸。

public  StringBuffer craeteSB(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}

StringBuffer sb是一個方法內部變量,上述代碼中直接將sb返回,這樣這個StringBuffer有可能被其他方法所改變,這樣它的作用域就不只是在方法內部,雖然它是一個局部變量,稱其逃逸到了方法外部。甚至還有可能被外部線程訪問到,稱爲線程逃逸或者方法返回值逃逸。

public  StringBuffer craeteSB(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}

不直接返回 StringBuffer,那麼StringBuffer將不會逃逸出方法。

逃逸分析好處:

1、同步省略。如果一個對象被發現只能從一個線程被訪問到,那麼對於這個對象的操作可以不考慮同步。
2、將堆分配轉化爲棧分配。如果一個對象在子程序中被分配,要使指向該對象的指針永遠不會逃逸,對象可能是棧分配的候選,而不是堆分配。
3、標量替換。有的對象可能不需要作爲一個連續的內存結構存在也可以被訪問到,那麼對象的部分(或全部)可以不存儲在內存,而是存儲在CPU寄存器中。用標量來替代聚合量
*注意hotSpot虛擬機沒有真正實現棧上分配而是依賴標量替換*

再回過頭來想一下剛纔的例子爲什麼沒有oom,應該是開啓了逃逸分析,爲了驗證,我們可以來實驗一下
關閉逃逸分析執行後的結果

[Full GC (Ergonomics) Exception in thread "main"  5083K->5076K(5632K), 0.0260039 secs]
[Full GC (Ergonomics)  5082K->5077K(5632K), 0.0131476 secs]
[Full GC (Ergonomics)  5082K->5079K(5632K), 0.0127679 secs]
[Full GC (Ergonomics)  5082K->5069K(5632K), 0.0135017 secs]
[Full GC (Ergonomics)  5082K->5072K(5632K), 0.0212518 secs]
[Full GC (Ergonomics)  5082K->5073K(5632K), 0.0145557 secs]
java.lang.OutOfMemoryError: GC overhead limit exceeded

可以看到對象現在是在堆上分配,因爲沒有足夠的內存直接OOM
好了到現在爲止,關於計算對象內存大小的問題都講完了。上邊主要計算的是當前對象的大小,如果當前對象包含引用變量,這些引用變量對應的大小是沒有計算的,但是在實際項目中也是需要計算的
接下來我們做個練習

  Map<Integer, EmptyTuan> emptyTuans = new HashMap<Integer, EmptyTuan>();

如上,創建100W個EmptyTuan 加入到 emptyTuans 這時總堆佔多大內存
首先先看一下一個Integer對象佔多大內存:

java.lang.Integer object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4    int Integer.value                             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

一個Integer佔16字節
通過上邊計算得到一個 EmptyTuan 對象佔16字節
再來計算一個hashMap佔多少字節

java.util.HashMap object internals:
 OFFSET  SIZE                       TYPE DESCRIPTION                               VALUE
      0    12                            (object header)                           N/A
     12     4              java.util.Set AbstractMap.keySet                        N/A
     16     4       java.util.Collection AbstractMap.values                        N/A
     20     4                        int HashMap.size                              N/A
     24     4                        int HashMap.modCount                          N/A
     28     4                        int HashMap.threshold                         N/A
     32     4                      float HashMap.loadFactor                        N/A
     36     4   java.util.HashMap.Node[] HashMap.table                             N/A
     40     4              java.util.Set HashMap.entrySet                          N/A
     44     4                            (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

通過計算是48字節
計算一個 Node<K,V>的大小

  static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
}

大小=12(頭)+16(data)+4(padding)=32字節
忽略其他因素:總內存大小 (100W*(16+16+32)+48)/1024/1024= 61M
所以大約需要61M左右
通過 jvisualvm看一下內存
內存對象
發現和我們預估的基本一致。

再回過頭來想一下,我開篇說到的是否都理解了。

注:本文運行環境是 jdk1.8

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章