天天看點

如何得知Java對象大小

在了解對象大小之前,先來複習下基本資料大小

資料類型 位元組大小
double 8
long
float 4
int
short 2
char
byte 1
boolean

對象大小分析

普通對象執行個體

如何得知Java對象大小

數組對象執行個體

如何得知Java對象大小

注:數組執行個體對象中對象頭多了一個記錄數組長度的int類型對象,占4位元組

對象頭!

HotSpot虛拟機

虛拟機的對象頭包括兩個部分資訊:

markword和klass,第一部分markword,用于儲存對象的運作時資料,哈希碼(HashCode)、GC年齡分代、鎖狀态标志、線程持有的鎖、偏向線程ID、偏向時間戳等。另一部分klass指針,即對象指向它的類中繼資料的指針,虛拟機通過這個指針來确定這個對象是哪個類的。

對象頭占用空間

######1.在32位系統下,存放Class指針的空間大小是4位元組,MarkWord是4位元組,對象頭為8位元組。

2.在64位系統下,存放Class指針的空間大小是8位元組,MarkWord是8位元組,對象頭為16位元組。

3.在64位開啟指針壓縮的情況下( -XX:+UseCompressedOops),存放Class指針的空間大小是4位元組,MarkWord是8位元組,對象頭為12位元組。

注:開啟指針壓縮要求記憶體必須在4GB~32GB,因為32位指針尋址4GB,按8位元組對齊,4*8=32GB,按更大對齊可以尋址更大空間,但是浪費就更大了。

4.如果對象是數組,那麼另外還要加4位元組

注:指針壓縮不能壓縮markword,指向非堆(Heap)的對象指針,局部變量、傳參、傳回值、NULL指針

執行個體資料

執行個體資料是對象儲存的有效資訊,也是程式代碼中所定義的各種類型的字段内容。無論是從父類繼承下來的,還是從子類中定義的,都需要記錄起來

對齊填充

最後一塊對齊填充空間并不是必然存在的,也沒有特别的含義,它僅僅起着占位符的作用。這是由于HotSpot VM的自動記憶體管理系統要求對象起始位址必須是8位元組的整數倍,換句話說,就是對象的大小必須是8位元組的整數倍。

注:對齊填充其實是兩次 ,對象的基本資料類型需要一次對齊填充,對象的引用類型也需要一次對齊填充,下面會詳細介紹

如何檢視對象的大小(64位操作環境)

1.使用jol-core

百度連結:

https://pan.baidu.com/s/1bYsh3y6DHcBQbKi6a_FcSQ

提取碼:l1nq 。

1.使用ClassLayout.parseInstance(Object instance).toPrintable();将對象大小以表格形式輸出

空對象

(此處的空對象是指類中沒有任何基礎類型和引用,不是對象=null)

import org.junit.Test;
import org.openjdk.jol.info.ClassLayout;

public class MyTest {

    @Test
    public void test(){
        A a = new A();      
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
class A{

}           

關閉指針壓縮

( -XX:-UseCompressedOops)

類A沒有值類型和引用類型 對象大小應該為 8(markword)+8(klass) 16byte

運作結果

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)                           f0 e2 ba 17 (11110000 11100010 10111010 00010111) (398123760)
     12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes           

開啟指針壓縮

對象大小應該為 8(markword)+4(klass)因為對象大小最後要能被8整除,是以還要是以還要+4的的填充對齊 ,最後大小還是16byte

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)                           c8 16 01 20 (11001000 00010110 00000001 00100000) (536942280)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes           

注:64位系統記憶體大于4GB且小于32GB JVM預設開啟指針壓縮。

普通對象

import org.junit.Test;
import org.openjdk.jol.info.ClassLayout;

public class MyTest {

    @Test
    public void test(){
        A a = new A();      
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
class A{
    int a;
    float b;
    long c;
    String d;
}           

對象大小應該是 8(markword)+8(Klass)+4(int)+4(float)+8(long)+8(string)(引用指針)40byte

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)                           58 e3 e9 17 (01011000 11100011 11101001 00010111) (401204056)
     12     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     16     8               long A.c                                       0
     24     4                int A.a                                       0
     28     4              float A.b                                       0.0
     32     8   java.lang.String A.d                                       null
Instance size: 40 bytes           

對象大小應該是 8(markword)+4(Klass)+4(int)+4(float)+8(long)+4(string)(因為開啟了指針壓縮是以引用指針也是4byte)32byte

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)                           c8 16 01 20 (11001000 00010110 00000001 00100000) (536942280)
     12     4                int A.a                                       0
     16     8               long A.c                                       0
     24     4              float A.b                                       0.0
     28     4   java.lang.String A.d                                       null
Instance size: 32 bytes           

開啟/關閉指針壓縮的結果差別:

主要差別就是讓原本占用8位元組的指針縮小到4位元組,另外未開啟指針壓縮時,上面提到的基本類型記憶體填充将會以8對齊,開啟時以4位元組對齊。但是對象尾部的填充不管是否開啟都是以8位元組對齊。

以下為代碼示範:

public class MyTest {

    @Test
    public void test(){
        A a = new A();
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
class A{
    int a;
    short b;//2byte
    float c;
    String d;

}           

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)                           58 e3 6b 17 (01011000 11100011 01101011 00010111) (392946520)
     12     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
     16     4                int A.a                                       0
     20     4              float A.c                                       0.0
     24     2              short A.b                                       0
     26     6                    (alignment/padding gap)                  
     32     8   java.lang.String A.d                                       null
Instance size: 40 bytes           

可以看到引用類型之前還有一次填充 ,向8補齊

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)                           c8 16 01 20 (11001000 00010110 00000001 00100000) (536942280)
     12     4                int A.a                                       0
     16     4              float A.c                                       0.0
     20     2              short A.b                                       0
     22     2                    (alignment/padding gap)                  
     24     4   java.lang.String A.d                                       null
     28     4                    (loss due to the next object alignment)
Instance size: 32 bytes           

引用類型之前按4位元組對齊 ,對象尾部按8位元組對齊

數組對象

注:基礎變量數組是對象!!

public class MyTest {

    @Test
    public void test(){
        A[] a = new A[3];
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}
class A{
    int a;
    short b;
    String [] d;

}           
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)                           07 17 01 20 (00000111 00010111 00000001 00100000) (536942343)
     12     4                      (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
     16    12   com.easebuy.test.A A;.<elements>                             N/A
     28     4                      (loss due to the next object alignment)
Instance size: 32 bytes           

可以看到數組對象頭多了一個用于記錄數組長度的int型變量,同時引用的對象大小也變成了 4*(數組長度)

用這種方式隻可以看到目前對象的大小,它所引用的對象執行個體大小是不會計算在裡面的。如果要計算目前對象和對象引用的所有對象執行個體可以使用下面介紹的這個方法

2.基于JDK8

JDK1.8有一個類

jdk.nashorn.internal.ir.debug.ObjectSizeCalculator

可以評估出對象的大小

public class MyTest {

    @Test
    public void test(){
        A[] a = new A[3];
        System.out.println(ObjectSizeCalculator.getObjectSize(a));
    }
}
class A{
    int a;
    short b;
    String [] d;

}           
32           

可以看到和上面使用的方法得到的值是一樣的,那是因為數組對象隻是聲明了3個。并沒有去執行個體它們。

下面是執行個體過的數組對象

public class MyTest {

    @Test
    public void test(){
        A[] a = {new A(),new A(),new A()};
        System.out.println(ObjectSizeCalculator.getObjectSize(a));
    }
}
class A{
    int a;
    short b;
    String [] d;

}           
104           

可以看到使用這個方法計算對象大小很友善。

總結

“對象在jvm中不是完全連續的,因為存在堆中,還有垃圾回收器的機制影響,總會出現散亂的記憶體,這就導緻了JVM必須為每個對象配置設定一段記憶體空間來儲存其引用的指針,再結合對象其他必須的中繼資料,使得對象在持有真實資料的基礎上還需要維護額外的資料。”

”在寫Java代碼需要注意這些JVM記憶體陷阱。“

參考連結&詳細了解

https://www.cnblogs.com/ulysses-you/p/10060463.html https://www.cnblogs.com/SunDexu/p/3140790.html https://blog.csdn.net/ignorewho/article/details/80840290

抱怨身處黑暗 不如提燈前行

菅江晖