Java對象大小內幕淺析

來源:互聯網
上載者:User

Java對象大小內幕淺析

最近突發奇想,忽然對Java對象的記憶體大小感興趣,去網上搜集了一些資料,並且做一下整理,希望能夠各位協助。
如果:你能算出new String(“abc”)這個對象在JVM中佔用記憶體大小(64位JDK7中壓縮大小48B,未壓縮大小64B), 那麼看到這裡就可以結束了~

Java對象的記憶體布局:對象頭(Header),執行個體資料(Instance Data)和對齊填充(Padding)
虛擬機器的對象頭包括兩部分資訊,第一部分用於儲存物件自身的運行時資料,如hashCode、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳記等。這部分資料的長度在32位和64的虛擬機器(未開啟指標壓縮)中分別為4B和8B,官方稱之為”Mark Word”。
對象的另一部分是類型指標(kclass),即對象指向它的類別中繼資料的指標,虛擬機器通過這個指標來確定這個對象是那個類的執行個體。另外如果對象是一個Java數組,那再對象頭中還必須有一塊用於記錄數組長度的資料,因為虛擬機器可以通過普通Java對象的中繼資料資訊確定Java對象的大小,但是從數組的中繼資料中卻無法確定數組的大小。
對象頭在32位系統上佔用8B,64位系統上佔16B。 無論是32位系統還是64位系統,對象都採用8位元組對齊。Java在64位元模式下開啟指標壓縮,比32位元模式下,頭部會大4B(mark地區變位8B,kclass地區被壓縮),如果沒有開啟指標壓縮,頭部會大8B(mark和kclass都是8B),換句話說,
?HotSpot的對齊為8位元組對齊:(對象頭+執行個體資料+padding)%8 等於0 且 0<=padding<8。以下說明都是以HotSpot為基準。

在參考資料2中提到,再JDK5之後提供的java.lang.instrument.Instrumentation提供了豐富的對結構的等各方面的跟蹤和對象大小的測量API。但是這個東西需要採用java的agent代理才能使用,至於agent代理和Instrumentation這裡就不闡述了,我這裡只闡述其使用方式。
在參考資料3中提供了這個類,個人覺得很實用,代碼如下所附1所示(代碼比較長,索性就放到文章最後了):
這段代碼可以直接拷貝,然後將其打成jar包(命名為agent.jar,如果沒有打包成功,可以直接下載博主打包好的),注意在META-INF/MANIFEST.MF中添加一行:

Premain-Class: com.zzh.size.MySizeOf (注意":"後面的空格,否則會報錯:invalid header field.)

舉個案例,代碼如下(博主的系統是64位的,採用的是64位的JDK7):

import com.zzh.size.MySizeOf;public class ObjectSize{    public static void  main(String args[])    {        System.out.println(MySizeOf.sizeOf(new Object()));    }}

接下來進行編譯運行,步驟如下:

編譯(agent.jar放在目前的目錄下):javac -classpath agent.jar ObjectSize.java 運行:java -javaagent:agent.jar ObjectSize(輸出結果:16,至於這個結果的分析,稍後再闡述)

JDK6推出參數-XX:+UseCompressedOops,在32G記憶體一下預設會自動開啟這個參數。可以在運行參數中添加-XX:-UseCompressedOops來關閉指標壓縮。
使用Instrumentation來測試對象的大小,只是為了更加形象的表示一個對象的大小,實際上當一個對象建立起來的時候可以手動計算其大小,代碼案例實踐用來證明理論知識的合理性及正確性,具體演算法在下面的代碼案例中有所體現。

補充:原生類型(primitive type)的記憶體佔用如下:

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

參考型別在32位系統上每個佔用4B, 在64位系統上每個佔用8B。

案例分析
扯了這麼多犢子,估計看的玄乎玄乎的,來幾段代碼案例來實踐一下。

案例1:上面的new Object()的大小為16B,這裡再重申一下,博主測試機是64位的JDK7,如無特殊說明,預設開啟指標壓縮。

new Object()的大小=對象頭12B(8Bmak區,4Bkclass區)+padding的4B=16B

案例2

    static class A{        int a;    }    static class B{        int a;        int b;    }    public static void  main(String args[])    {        System.out.println(MySizeOf.sizeOf(new Integer(1)));        System.out.println(MySizeOf.sizeOf(new A()));        System.out.println(MySizeOf.sizeOf(new B()));    }

輸出結果:

(指標壓縮) 16    16    24(指標未壓縮)24    24    24

分析1(指標壓縮):

new Integer(1)的大小=12B對象頭+4B的執行個體資料+0B的填充=16Bnew A()的大小=12B對象頭+4B的執行個體資料+0B的填充=16Bnew B()的大小=12B對象頭+2*4B的執行個體資料=20B,填充之後=24B

分析2(指標未壓縮):

new Integer(1)的大小=16B對象頭+4B的執行個體資料+4B的填充=24Bnew A()的大小=16B對象頭+4B的執行個體資料+4B的填充=24Bnew B()的大小=16B對象頭+2*4B的執行個體資料+0B的填充=24B

案例3

System.out.println(MySizeOf.sizeOf(new int[2]));System.out.println(MySizeOf.sizeOf(new int[3]));System.out.println(MySizeOf.sizeOf(new char[2]));System.out.println(MySizeOf.sizeOf(new char[3]));

輸出結果:

(指標壓縮) 24    32    24    24(指標未壓縮) 32    40    32    32

分析1(指標壓縮):

new int[2]的大小=12B對象頭+壓縮情況下數組比普通對象多4B來存放長度+2*4B的int執行個體大小=24Bnew int[3]的大小=12B對象頭+4B長度+3*4B的int執行個體大小=28B,填充4B =32Bnew char[2]的大小=12B對象頭+4B長度+2*2B的執行個體大小=20B,填充4B=24Bnew char[3]的大小=12B對象頭+4B長度+3*2B的執行個體大小+2B填充=24B(PS:new char[5]的大小=32B)

分析2(指標未壓縮):

new int[2]的大小=16B對象頭+未壓縮情況下數組比普通對象多8B來存放長度+2*4B執行個體大小=32Bnew int[3]的大小=16B+8B+3*4B+4B填充=40Bnew char[2]的大小=16B+8B+2*2B+4B填充=32Bnew char[2]的大小=16B+8B+3*2B+2B填充=32B(PS:new char[5]的大小為40B)

案例4(sizeOf只計算本體對象大小,fullSizeOf計算本體對象大小和引用的大小,具體可以翻閱附錄1的代碼).

System.out.println(MySizeOf.sizeOf(new String("a")));System.out.println(MySizeOf.fullSizeOf(new String("a")));System.out.println(MySizeOf.fullSizeOf(new String("aaaaa")));

輸出結果:

(指標壓縮)24    48    56    (指標未壓縮)32    64   72  

分析1(指標壓縮):

翻看String(JDK7)的源碼可以知道,String有這幾個成員變數:(static變數屬於類,不屬於執行個體,所以聲明為static的不計入對象的大小)private final char value[];private int hash;private transient int hash32 = 0;MySizeOf.sizeOf(new String("a"))的大小=12B對象頭+2*4B(成員變數hash和hash32)+4B(壓縮的value指標)=24BMySizeOf.fullSizeOf(new String("a"))的大小=12B對象頭+2*4B(成員變數hash和hash32)+4B指標+(value數組的大小=12B對象頭+4B數組長度+1*2B執行個體大小+6B填充=24B)=12B+8B+4B+24B=48B(PS: new String("aa"),new String("aaa"),new String("aaaa")的fullSizeOf大小都為48B)MySizeOf.fullSizeOf(new String("aaaaa"))的大小=12B+2*4B+4B+(12B+4B+5*2B+6B填充)=24B+32B=56B

分析2(指標未壓縮)

MySizeOf.sizeOf(new String("a"))的大小=16B+2*4B+8B(位壓縮的指標大小) =32BMySizeOf.fullSizeOf(new String("a"))的大小=16B對象頭+2*4B(成員變數hash和hash32)+8B指標+(value數組的大小=16B對象頭+8B數組長度+1*2B執行個體大小+6B填充=32B)=32B+32B=64B(PS: new String("aa"),new String("aaa"),new String("aaaa")的fullSizeOf大小都為64B)MySizeOf.fullSizeOf(new String("aaaaa"))的大小=16B+2*4B+8B+(16B+8B+5*2B+6B填充)=32B+40B=72B

這些計算結果只會少不會多,因為在代碼運行過程中,一些對象的頭部會伸展,mark地區會引用一些外部的空間(輕量級鎖,偏向鎖,這裡不展開),所以官方給出的說明也是,最少會佔用多少位元組,絕對不會說只佔用多少位元組。

如果是32位的JDK,可以算一下或者運行一下上面各個案例的結果。

看來上面的這些我們來手動計算下new String()的大小:
1. 指標壓縮的情況

12B對象頭+2*4B執行個體變數+4B指標+(12B對象頭+4B數組長度大小+0B執行個體大小)=24B+16B=40B
指標未壓縮的情況
16B+2*4B+8B指標+(16B+8B數組長度大小+0B)=32B+24B=56B

所以一個空的String對象最少也要佔用40B的大小,所以大家在以後應該編碼過程中要稍微注意下。其實也並不要太在意,相信能從文章開頭看到這裡的同學敲的代碼也數以萬計了,不在意這些也並沒有什麼不妥之處,只不過如果如果你瞭解了的話對於提升自己的逼格以及代碼最佳化水平有很大的協助,比如:能用基本類型的最好別用其封裝類。

附:agent.jar包源碼

package com.zzh.size;import java.lang.instrument.Instrumentation;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.lang.reflect.Modifier;import java.util.ArrayDeque;import java.util.Deque;import java.util.HashSet;import java.util.Set;public class MySizeOf{     static Instrumentation inst;          public static void premain(String args, Instrumentation instP) {              inst = instP;          }          /**          * 直接計算當前對象佔用空間大小,包括當前類及超類的基本類型執行個體欄位大小、          * 參考型別執行個體欄位引用大小、執行個體基本類型數組總佔用空間、執行個體參考型別數組引用本身佔用空間大小;          * 但是不包括超類繼承下來的和當前類聲明的執行個體引用欄位的對象本身的大小、執行個體引用數組引用的對象本身的大小           *          * @param obj          * @return          */          public static long sizeOf(Object obj) {              return inst.getObjectSize(obj);          }          /**          * 遞迴計算當前對象佔用空間總大小,包括當前類和超類的執行個體欄位大小以及執行個體欄位引用對象大小          *          * @param objP          * @return          * @throws IllegalAccessException          */          public static long fullSizeOf(Object objP) throws IllegalAccessException {              Set visited = new HashSet();              Deque toBeQueue = new ArrayDeque<>();              toBeQueue.add(objP);              long size = 0L;              while (toBeQueue.size() > 0) {                  Object obj = toBeQueue.poll();                  //sizeOf的時候已經計基本類型和引用的長度,包括數組                  size += skipObject(visited, obj) ? 0L : sizeOf(obj);                  Class tmpObjClass = obj.getClass();                  if (tmpObjClass.isArray()) {                      //[I , [F 基本類型名字長度是2                      if (tmpObjClass.getName().length() > 2) {                          for (int i = 0, len = Array.getLength(obj); i < len; i++) {                              Object tmp = Array.get(obj, i);                              if (tmp != null) {                                  //非基本類型需要深度遍曆其對象                                  toBeQueue.add(Array.get(obj, i));                              }                          }                      }                  } else {                      while (tmpObjClass != null) {                          Field[] fields = tmpObjClass.getDeclaredFields();                          for (Field field : fields) {                              if (Modifier.isStatic(field.getModifiers())   //靜態不計                                      || field.getType().isPrimitive()) {    //基本類型不重複計                                  continue;                              }                              field.setAccessible(true);                              Object fieldValue = field.get(obj);                              if (fieldValue == null) {                                  continue;                              }                              toBeQueue.add(fieldValue);                          }                          tmpObjClass = tmpObjClass.getSuperclass();                      }                  }              }              return size;          }          /**          * String.intern的對象不計;計算過的不計,也避免死迴圈          *          * @param visited          * @param obj          * @return          */          static boolean skipObject(Set visited, Object obj) {              if (obj instanceof String && obj == ((String) obj).intern()) {                  return true;              }              return visited.contains(obj);          }  }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.