对象内存大小度量

1 对象内存大小度量

原文:http://www.liaohuqiu.net/cn/posts/caculate-object-size-in-java/补充了文中的细节,做了一些订正,加粗斜体是补充和订正

在做内存优化时,需要知道每个对象占用的内存的大小,

一个实例化的对象在内存中需要存储的信息包括:

1.           对象的头部(对象的GC信息,hash值,类定义引用等)

2.           对象的成员变量:包括基本数据型和引用。如成员变量是一个引用,引用了其他象,被引用的象内存另外算。

如下一个简单的类的定义:

class MyClass {

    int a;

    Object object;

}

1.1 实例化一个对象

MyClass myClass = new MyClass();

对象大小分为:

1.1.1自身的大小(Shadow heap size):

直接计算当前对象占用空间大小,包括当前类及超类的基例字段大小、引用例字段引用大小、实例基本型数总占用空间、实例引用型数引用本身占用空间大小

 

1.1.2所引用的对象的大小(Retained heap size):

myClass实例创建出来之后,在内存中所占的大小就是myClass自身大小(Shadow heap size)。包括类的头部大小以及一个int的大小和一个引用的大小。

myClass object 成员变量是一个对象引用,这个被引用的对象也占一定大小。myClass实例所维护的引用的对象所占的大小,称为myClass实例的Retained heap size

本文讨论的是对象自身的大小,即Shadow heap sizeRetained heap size递归计算即可得。

2度量工具

对象大小的计算可用java.lang.instrument.Instrumentation 或者 dump内存之后用memory analyzer分析。这是一份示例代码java-object-size

2.1基本数据类型大小

基本数据类型大小如下From WIKI

type

size(bits)

bytes

boolean

8

1

byte

8

1

char

16

2

short

16

2

int

32

4

long

64

8

float

32

4

double

64

8

2.2引用的大小

32位的JVM上,一个对象引用占用4个字节;在64位上,占用8个字节。通过 java -d64 -version可确定是否是64位的JVM

使用8个字节是为了能够管理大于4G的内存,如果你的程序不需要访问大于4G的内存,

可通过-XX:+UseCompressedOops选项,开启指针压缩。从Java 1.6.0_23起,这个选项默认是开的。可通过jinfo -flag UseCompressedOops <pid>查看。

localhost:~ srain$ jinfo -flag UseCompressedOops 13133

-XX:+UseCompressedOops

2.3对象头部的大小

对象头,结构如下(来源):

+------------------+------------------+------------------+---------------.

|    mark word     |  klass pointer  |  array size (opt) |    padding   |

+------------------+------------------+-------------------+---------------'

每个对象都有一个mark work头部,以及一个引用,指向类的信息。在32JVM上,markword 4个字节,整个头部有8字节大小。

array size(opt)在对象为数组的时候启用,4字节(4byte)长度。JVM规定对象头(Object Header)长度为2个字(word),在32bit JVM中,一个word长度为4byte64bit JVM中,长度为8byte

在未开启UseCompressedOops64JVM上,对象头有16字节大小。

在开启UseCompressedOops64位机器上,引用(klass pointer)成了4字节,一共12字节。按照8位对齐,实际占用16字节。

 

java64bit模式下开启指针压缩,比32bit模式下,头部会大4byte_mark区域变成8byte_class区域被压缩),如果没有开启指针压缩,头部会大8byte_mark_class都会变成8byte),jdk1.6推出参数-XX:+UseCompressedOops,在32G内存一下默认会自动打开这个参数,如下:

 [Nathan@laptop ~]$ java -Xmx31g -XX:+PrintFlagsFinal |grep Compress  

     bool SpecialStringCompress       = true            {product}           

     bool UseCompressedOops           = true            {lp64_product}      

     bool UseCompressedStrings        = false           {product}   

[Nathan@laptop ~]$ java -Xmx32g -XX:+PrintFlagsFinal |grep Compress  

     bool SpecialStringCompress       = true            {product}           

     bool UseCompressedOops           = false           {lp64_product}      

     bool UseCompressedStrings        = false           {product}   

 

 

3对象的内存布局

1.           每个对象的内存占用按8字节对齐

2.           空对象和类实例成员变量

空对象,指的非inner-class,没有实例属性的类。Object 类或者直接继承Object 没有添加任何实例成员的类,即:new Object() 

空对象的不包含任何成员变量,其大小即对象头大小:

o   32JVM上,占用8

o   在开启UseCompressedOops64JVM上,12 + 4 = 16

o   正:在开启UseCompressedOops64JVM上,object header12(mark word: 8, kclass pointer: 4),padding4对齐后的16

o   在未开启UseCompressedOops64JVM上,16 + 4 = 20; 对齐后为24

o   正:在未开启UseCompressedOops64JVM上,度16(mark word:8 ,klass pointer 8)

3.           对象实例成员重排序

实例成员变量紧随对象头。每个成员变量都尽量使本身的大小在内存中尽量对齐。

比如int4位对齐,long8位对齐。为了内存紧凑,实例成员在内存中的排列和声明的顺序可能不一致,实际会按以下顺序排序:

1.  doubles andlongs

2.  ints and floats

3.  shorts andchars

4.  booleans andbytes

5.  references

这样做可尽量节省空间。

如:

classMyClass{

    byte a;

    int c;

    boolean d;

    long e;

    Object f;       

}

未重排之前:

     32 bit                                  64bit +UseCompressedOops

 

[HEADER: 12 bytes]  8          [HEADER: 12 bytes] 12

[a:       1 byte ] 9                    [a:       1 byte ] 13

[padding: 3 bytes] 12           [padding: 3 bytes] 16

[c:       4 bytes] 16                 [c:       4 bytes] 20

[d:       1 byte ] 17                 [d:       1 byte ] 21

[padding: 7 bytes] 24           [padding: 3 bytes] 24

[e:       8 bytes] 32               [e:       8 bytes] 32

[f:       4 bytes] 36               [f:      4 bytes] 36

[padding: 4 bytes] 40           [padding: 4 bytes] 40

重新排列之后:

    32 bit                      64bit +UseCompressedOops

 

[HEADER:  8 bytes] 8           [HEADER: 12 bytes] 12

[e:       8 bytes] 16           [e:       8 bytes] 20

[c:       4 bytes] 20           [c:       4 bytes] 24

[a:       1 byte ] 21           [a:       1 byte ] 25

[d:       1 byte ] 22           [d:       1 byte ] 26

[padding: 2 bytes] 24     [padding: 2 bytes] 28

[f:       4 bytes] 28           [f:       4 bytes] 32

[padding: 4 bytes] 32

4.  父类和子类的实例成员

父类和子类的成员变量分开存放,先是父类的实例成员。父类实例成员变量结束之后,按4位对齐,随后接着子类实例成员变量。

classA{

    byte a;

}

 

classBextends A{

    byte b;

}

内存结构如下:

    32 bit                               64bit +UseCompressedOops

 

[HEADER:  8 bytes] 8       [HEADER: 12 bytes] 12

[a:       1 byte ] 9               [a:       1 byte ] 13

[padding: 3 bytes] 12       [padding: 3 bytes] 16

[b:       1 byte ] 13             [b:      1 byte ] 17

[padding: 3 bytes] 16       [padding: 7 bytes] 24

如果子类首个成员变量是long或者double等8字节数据类型,而父类结束时没有8位对齐。会把子类的小于8字节的实例成员先排列,直到能8字节对齐。

class A {

    byte a;

}

 

class B extends A{

    long b;

    short c; 

    byte d;

}

内存结构如下:

    32 bit                            64bit +UseCompressedOops

 

[HEADER:  8 bytes] 8       [HEADER:  8 bytes] 12

[a:       1 byte ]  9              [a:       1 byte ] 13

[padding: 3 bytes] 12       [padding: 3 bytes] 16

[c:       2 bytes] 14           [b:      8 bytes] 24

[d:       1 byte ] 15           [c:      4 byte ] 28

[padding: 1 byte ] 16       [d:      1 byte ] 29

[b:       8 bytes] 24          [padding: 3 bytes] 32

上面的示例中,在32位的JVM上,B的2个实例成员c, d被提前了。

5.  非静态的内部类,有一个隐藏的对外部类的引用。

3.1数组的内存占用大小

数组也是对象,故有对象的头部,另外数组还有一个记录数组长度的int型,随后是每一个数组的元素:基本数据类型或者引用。8字节对齐。

32 位的机器上

byte[0] 8字节的对象头部,4字节的int长度, 12字节,对齐后是16字节,实际 byte[0] ~byte[4]都是16字节(每个byte的长度为1byte,byte[0]-byte[4]占用padding长度)。

64 位+UseCompressedOops

byte[0] 16字节大小(对象头:mark word: 8byte, kclasspointer: 4byte, array sizeof: 4byte),byte[1] ~byte[8] 24字节大小。

64 位-UseCompressedOops

byte[0], 16字节头部,4字节的int长度信息,20字节,对齐后 24 字节。byte[0] ~ byte[4] 都是24字节。

 

计算一个数组对象的公式(-XX:UseCompressedOops

The general rules forcomputing the size of an object on JVM are :

32 bit 



Arrays of boolean, byte, char, short, int: 2 * 4 (Object header)+ 4 (length-field) + sizeof(primitiveType) * length -> align result up to amultiple of 8

Arrays of objects: 2 * 4 (Object header) + 4 (length-field) + 4(objectreference length) * array length -> align result up to a multiple of 8

Arrays of longs and doubles: 2 * 4 (Object header) + 4 (length-field) + 4 (deadspace due to alignment restrictions) + 8 * length

java.lang.Object: 2 * 4 (Object header) 


other objects: sizeofSuperClass + 8 * nrOfLongAndDoubleFields + 4 *nrOfIntFloatAndObjectFields + 2 * nrOfShortAndCharFields + 1 *nrOfByteAndBooleanFields -> align result up to a multiple of 8



64 bit


Arrays of boolean, byte, char, short, int: 2 * 8 (Object header)+ 4 (length-field) + sizeof(primitiveType) * length -> align result up to amultiple of 8

Arrays of objects: 2 * 8 (Object header) + 4 (length-field) + 4 (dead space dueto alignment restrictions) + 8 * length

Arrays of longs and doubles: 2 * 8 (Object header) + 4 (length-field) + 4 (deadspace due to alignment restrictions) + 8 * length

java.lang.Object: 2 * 8 (Object header)

other objects: sizeofSuperClass + 8 * nrOfLongDoubleAndObjectFields + 4 +nrOfntAndFloatFields + 2 * nrOfShortAndCharFields + 1 *nrOfByteAndBooleanFields -> align result up to a multiple of 8




Note thatan object might have unused space due to alignment at every inheritance level(e.g. imagine a class A with just a byte field and class B has A as it'ssuperclass and declares a byte field itself -> 14 bytes 'wasted on 64 bitsystem).


Inpractice 64 bit needs 30 to 50% more memory due to references being twice aslarge. 

 

 

3.2字符串大小

Field

Type

64 bit -UseCompressedOops

64 bit +UseCompressedOops

32 bit

HEADER

16

12

8

value

char[]

8(引用)

4

4

offset

int

4

4

4

count

int

4

4

4

hash

int

4

4

4

PADDING

4

4

0

TOTAL

40

32

24


不计算value引用的Retained heap size,字符串本身就需要 24 ~ 40字节大小。

一个newString("a");+UseCompressedOops这个对象的空间大小为:12字节头部+4*4 = 28字节对齐到32字节,然后value所指向的char数组头部比普通对象多4byte来存放长度,12+4+2byte的字符=18,总大小为28+18=46byte对齐后就是48byte,其实即使你new String()也会占这么大的空间,28+16=44byte,因为有对齐,如果字符的长度是8个,那么就是12+4+16=32,也就是有64byte

如果不开启指针压缩再算算:头部变成16byte + 4*3int数据 + 81个指针) = 36对齐到40byte,对应的char数组的头部变成16+4 + 2 = 22对齐到24byte40+24=64byte,也就是只有一个字符或者0个字符都会对齐到64byte,所以,你懂的,参数该怎么调,代码该怎么写,如果长度为8个字符的那么后面部分就会变成16+4+16=36对齐到40byte40+40=80byte,也就是说,抛开其他的引用空间(比如通过数组或集合类引用),如果你有10来个String,每个大小就装8个字符,就会有1K的大小

 

4参考资料


http://kohlerm.blogspot.com/2008/12/how-much-memory-is-used-by-my-java.html

 

http://www.liaohuqiu.net/cn/posts/caculate-object-size-in-java/

 

http://blog.csdn.net/xieyuooo/article/details/7068216

 

http://yueyemaitian.iteye.com/blog/2033046

 

http://www.cnblogs.com/zhanjindong/p/3757767.html

 

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值