Java对象内存大小计算


JavaClass基本结构:

名称 占用字节
Class头 8字节
oop指针 4字节
数据区域 不定
对其补充 补充到整个大小为8字节的倍数
1. Class头8个字节, 存储了比如这个实例目前的锁信息、目前属于的堆类型等
2. oop指针,存储的是这个类的定义,比如Java反射可以拿到字段名称,方法名称这些值都是存储在这个指针所指向的定义中
3. 数据区域,存放数据的区域,这里的结构区分主要是两种:数组和非数组。如果是数组,数据区域中还会包含这个数组的大小

计算Java对象内存大小有三种方式:

  1. AgentSizeOf : 使用jvm代理和Instrumentation
  2. UnsafeSizeOf : 使用unsafe
  3. ReflectionSizeOf : 通过反射出来Class的成员,通过成员类型进行计算

实例数据:

原生类型(primitive type)的内存占用如下:

Primitive Type Memory Required(bytes)
boolean                       在数组中占1个字节,单独使用时占4个字节
byte                             1
short                            2
char                             2
int                                4
float                             4
long                             8
double     8

reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes。

 

关于boolean内存占用 https://www.cnblogs.com/wangtianze/p/6690665.html?utm_source=itdadao&utm_medium=referral

 

对齐填充

HotSpot的对齐方式为8字节对齐:

(对象头 + 实例数据 + padding) % 8等于0且0 <= padding < 8

 

指针压缩

对象占用的内存大小收到VM参数UseCompressedOops的影响。32G内存以下的,默认开启对象指针压缩。

1)对对象头的影响

开启(-XX:+UseCompressedOops)对象头大小为12bytes(64位机器)。

static class A {
 
    int a;
 
}

A对象占用内存情况:

关闭指针压缩: 16(对象头)+4(实例数据)=20不是8的倍数,因此需要对齐填充 16+4+4(padding)=24

开启指针压缩: 12+4=16已经是8的倍数了,不需要再padding。

2) 对reference类型的影响

64位机器上reference类型占用8个字节,开启指针压缩后占用4个字节。

static class B2 {
    int b2a;
    Integer b2b;
}

B2对象占用内存情况:

关闭指针压缩: 16+4+8=28不是8的倍数,需要对齐填充 16+4+8+4(padding)=32

开启指针压缩: 12+4+4=20不是8的倍数,需要对齐填充12+4+4+4(padding)=24

数组对象

64位机器上,数组对象的对象头占用24个字节(8字节MarkWord+8字节类型指针+8字节数组长度),启用压缩之后占用16个字节(8字节MarkWord+4字节类型指针+4字节数组长度)。之所以比普通对象占用内存多是因为需要额外的对象头空间存储数组的长度。

先考虑下new Integer[0]占用的内存大小,数组长度为0,所以所占用的大小就是对象头的大小:

未开启压缩:24bytes

开启压缩后:16bytes

接着计算new Integer[1],new Integer[2],new Integer[3]和new Integer[4]就很容易了:

未开启压缩:

开启压缩:

拿new Integer[3]来具体解释下:

未开启压缩:24(对象头)+ 8*3 = 48,不需要padding;

开启压缩:16(对象头)+ 4*3 = 28,需要对齐填充 28 + 4(padding) = 32,其他依次类推。

自定义类的数组也是一样的,比如:

static class B3 {
    int a;
    Integer b;
}

new B3[3]占用的内存大小:

未开启压缩:24(对象头)+ 8*3 = 48

开启压缩后:16(对象头)+ 4*3 + 4(padding) = 32

复合对象

计算复合对象占用内存的大小其实就是运用上面几条规则,只是麻烦点。

1)对象本身的大小

直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小; 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小。

static class B {
    int a;
    int b;
}
static class C {
    int ba;
    B[] as = new B[3];
    C() {
        for (int i = 0; i < as.length; i++) {
            as[i] = new B(); 
        }
    }
}        

计算C对象的大小:

未开启压缩:16(对象头)+ 4(ba)+ 8(as引用的大小)+ 4(padding) = 32

开启压缩:12(对象头)+ 4(ba)+4(as引用的大小)+ 4(padding) = 24

2)当前对象占用的空间总大小

递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小。

递归计算复合对象占用的内存的时候需要注意的是:对齐填充是以每个对象为单位进行的,看下面这个图就很容易明白。

 现在我们来手动计算下C对象占用的全部内存是多少,主要是三部分构成:C对象本身的大小+数组对象的大小+B对象的大小。

未开启压缩:

(16 + 4 + 8+4(padding)) + (24+ 8*3) +(16+4+4)*3 = 152bytes

开启压缩:

(12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(数组对象padding)) + (12+4+4+4(B对象padding)) *3= 128bytes

继承关系

涉及继承关系的时候有一个最基本的规则:首先存放父类中的成员,接着才是子类中的成员, 父类也要按照 8 byte 规定

 public static class D {
    byte d1;
}
 
public static class E extends D {
    byte e1;
}

  

计算E对象的大小:

未开启压缩:16(对象头) + 父类(1(d1) + 7(padding)) + 1(e1) + 7(padding) = 32

开启压缩:12(对象头) + 父类(1(d1) + 7(padding)) + 1(e1) + 3(padding) = 24

 

 

参考:https://www.jianshu.com/p/b925b5b5610e?from=timeline&isappinstalled=0

  https://www.cnblogs.com/wangtianze/p/6690665.html?utm_source=itdadao&utm_medium=referral

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM