程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java對象大小內幕淺析

Java對象大小內幕淺析

編輯:JAVA綜合教程

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的填充=16B
new A()的大小=12B對象頭+4B的實例數據+0B的填充=16B
new B()的大小=12B對象頭+2*4B的實例數據=20B,填充之後=24B

分析2(指針未壓縮):

new Integer(1)的大小=16B對象頭+4B的實例數據+4B的填充=24B
new A()的大小=16B對象頭+4B的實例數據+4B的填充=24B
new 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實例大小=24B
new int[3]的大小=12B對象頭+4B長度+3*4B的int實例大小=28B,填充4B =32B
new char[2]的大小=12B對象頭+4B長度+2*2B的實例大小=20B,填充4B=24B
new char[3]的大小=12B對象頭+4B長度+3*2B的實例大小+2B填充=24B
(PS:new char[5]的大小=32B)

分析2(指針未壓縮):

new int[2]的大小=16B對象頭+未壓縮情況下數組比普通對象多8B來存放長度+2*4B實例大小=32B
new int[3]的大小=16B+8B+3*4B+4B填充=40B
new char[2]的大小=16B+8B+2*2B+4B填充=32B
new 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指針)=24B
MySizeOf.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(位壓縮的指針大小) =32B
MySizeOf.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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved