平常工作中,我们只new一个对象,却基本不关心这个对象到底占了我们多少空间.
今天就一起看下,对象的空间占用情况
首先,内存中的对象是由以下几部分构成的:
结合上图以下类为例,看下内存空间的占用情况
public class MyObject { int i = 123; long l = 234; String str = "12345";}
1. Mark word: 记录线程,锁等对象状态,64位机占用8字节;32位机占用4字节; 当前主机是64位占8字节
2. Klass pointer: 指向类元数据的指针,开启指针压缩时, 占4字节;
3. 数组长度: 如果是数组对象,存储数组长度,如果不是数组,没有该区域;
4. 头部数据补全: 只是在关闭指针压缩,并且是数组对象的情况下才会用到;
5. 实例属性: 存储实例属性; 例如:int变量4字节; long变量8字节; String对象是一个指针占4字节;
6. 对齐补全: JVM中开辟的内存空间必须是8字节的倍数, 如果缺少位数,需要补全为8的倍数; 以上各字段共28字节,需补全4字节
所以MyObject对象共占用8+4+0+16+4=32字节
下面利用openjdk的jol工具包验证下
public static void main(String[] args) { MyObject object = new MyObject(); System.out.println(ClassLayout.parseInstance(object).toPrintable());}
执行结果:
MyObject object internals: 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) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315) 12 4 int MyObject.i 123 16 8 long MyObject.l 234 24 4 java.lang.String MyObject.str (object) 28 4 (loss due to the next object alignment)Instance size: 32 bytesSpace losses: 0 bytes internal + 4 bytes external = 4 bytes total
和我们预期是一样,共占用32字节,有4字节的空间损失.
数组结构内存空间
再看下数组结构的内存空间分配
public static void main(String[] args) { MyObject[] array = new MyObject[]{new MyObject(),new MyObject()}; System.out.println(ClassLayout.parseInstance(array).toPrintable());}
对象头部分除MarkWord和klass pointer以外, 还有4字节用来存储数组长度
[LMyObject; object internals: 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) d1 c4 00 f8 (11010001 11000100 00000000 11111000) (-134167343) 12 4 (object header) 02 00 00 00 (00000010 00000000 00000000 00000000) (2) 16 8 MyObject [LMyObject;. N/AInstance size: 24 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total
其他类型的实例属性
再看看其他属性的内存占用情况是如何的.
public class OtherObject { public static final float PI = 3.14f; boolean b = false; char c = 'a'; short s = 20; int i = 123; long l = 234; float f = 0.1f; double d = 0.2d; String str = "12345"; Map map = new HashMap(); Set set = new HashSet(); List list = new ArrayList(); public static void main(String[] args) { OtherObject object = new OtherObject(); object.map.put("a", "a"); object.set.add("a"); object.list.add("a"); System.out.println(ClassLayout.parseInstance(object).toPrintable()); System.out.println(ClassLayout.parseInstance(object).headerSize()); System.out.println(ClassLayout.parseInstance(object).instanceSize()); }}
通过执行结果可以发现:
类变量是不用开辟内存空间的
boolean类型占用1个字节,后面需要3字节对齐补全;
short和char类型都需要2字节的空间, 但JVM进行了优化,并不需增加空间对齐补全;
在最后需要4字节对齐补全;
整个对象需要64字节,空间损失了7字节.
OtherObject object internals: 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) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315) 12 4 int OtherObject.i 123 16 8 long OtherObject.l 234 24 8 double OtherObject.d 0.2 32 4 float OtherObject.f 0.1 36 2 char OtherObject.c a 38 2 short OtherObject.s 20 40 1 boolean OtherObject.b false 41 3 (alignment/padding gap) 44 4 java.lang.String OtherObject.str (object) 48 4 java.util.Map OtherObject.map (object) 52 4 java.util.Set OtherObject.set (object) 56 4 java.util.List OtherObject.list (object) 60 4 (loss due to the next object alignment)Instance size: 64 bytesSpace losses: 3 bytes internal + 4 bytes external = 7 bytes total
指针压缩
指针压缩是指在64位机上,也使用32位表示引用地址.
在上面的例子中都是开启指针压缩的,因为从jdk6之后就是默认开启的.
共涉及到2个JVM参数
-XX:+UseCompressedOops
会使用4字节来表示java object的引;
默认开启
-XX:+UseCompressedClassesPointers
默认开启
用4字节来表示进程中的class pointer;
UseCompressedClassPointers的开启是依赖于UseCompressedOops的开启.
同样是MyObject对象,在关闭指针压缩时,共需要40字节的空间
Mark down使用8字节
Klass pointer 使用8字节
str指针也占用8字节;
MyObject object internals: 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) 28 30 60 a2 (00101000 00110000 01100000 10100010) (-1570754520) 12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 16 8 long MyObject.l 234 24 4 int MyObject.i 123 28 4 (alignment/padding gap) 32 8 java.lang.String MyObject.str (object)Instance size: 40 bytesSpace losses: 4 bytes internal + 0 bytes external = 4 bytes total
指针压缩的好处
1. 节省内存空间
2. 提升执行效率
头部空间补全
在关闭指针压缩之后,在看数组对象的内存空间,就可以发现产生了数据补全的情况
Mark down使用8字节
Klass pointer 使用8字节
数组长度 使用4字节
头部数据补全 使用4字节
[LMyObject; object internals: 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) 00 3a c2 0c (00000000 00111010 11000010 00001100) (214055424) 12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 16 4 (object header) 02 00 00 00 (00000010 00000000 00000000 00000000) (2) 20 4 (alignment/padding gap) 24 16 MyObject [LMyObject;. N/AInstance size: 40 bytesSpace losses: 4 bytes internal + 0 bytes external = 4 bytes total
以上,就是一个对象内存的占用情况.
最后,附上openjdk.jol的maven依赖
org.openjdk.jol 0.9 jol-cli