首先以一个问题引出这次的内容:Object o = new Obeject()
在内存中占了多少个字节?
要回答这个问题就要首先清楚对象在内存中的内存布局。
对象内存布局
根据java虚拟机规范里面的描述:java对象分为三部分:对象头(Object Header) = mark word + class point(对象的引用),实例数据(instance data,成员变量),对齐填充(padding)。如图:
数组与对象类似,只是对象头部分多了数组长度Length的存储长度为4字节。
测试代码:
工具:JOL = java Object Layout
引入jar包:
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.14</version>
</dependency>
public class T01 {
public static void main(String[] args) {
Object o = new Object();
ClassLayout classLayout = ClassLayout.parseInstance(o);
System.out.println(classLayout.toPrintable());
Long l = new Long(1);
ClassLayout classLayout1 = ClassLayout.parseInstance(l);
System.out.println(classLayout1.toPrintable());
ClassLayout classLayout2 = ClassLayout.parseInstance(new HashMap<>());
System.out.println(classLayout2.toPrintable());
}
}
运行结果:
java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
java.lang.Long 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) f5 22 00 f8 (11110101 00100010 00000000 11111000) (-134208779)
12 4 (alignment/padding gap)
16 8 long Long.value 1
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
java.util.HashMap 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) a8 37 00 f8 (10101000 00110111 00000000 11111000) (-134203480)
12 4 java.util.Set AbstractMap.keySet null
16 4 java.util.Collection AbstractMap.values null
20 4 int HashMap.size 0
24 4 int HashMap.modCount 0
28 4 int HashMap.threshold 0
32 4 float HashMap.loadFactor 0.75
36 4 java.util.HashMap.Node[] HashMap.table null
40 4 java.util.Set HashMap.entrySet null
44 4 (loss due to the next object alignment)
Instance size: 48 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
以Object为例,分析每个组成部分。
对象头
从图片上得知对象头分为两部分:Mark Word 与 Class Pointer(类型指针,可以理解是对象的引用)。
Mark Word存储了对象的hashCode、GC信息、锁信息三部分,Class Pointer存储了指向类对象信息的指针。在32位JVM上对象头占用的大小是8字节,64位JVM则是16字节,两种类型的Mark Word 和 Class Pointer各占一半空间大小。
注意:在64位JVM上有一个压缩指针选项**-XX:+UseCompressedOops**,默认是开启的。开启之后Class Pointer部分就会压缩为4字节,此时对象头大小就会缩小到12字节。
计算机位数 | 是否开启压缩指针 | mark word | class pointer |
64位 | 是 | 8 | 4 |
64位 | 否 | 8 | 8 |
32位 | \ | 4 | 4 |
instance data 实例数据
这部分不仅存了成员变量的数据, 而且也有为了内存对齐的填充数据,即:instance data = 成员变量值 + padding数据。
为了实现这个内存对齐的效果,jvm不仅会padding而且还执行一个字段重排序的操作。
字段重排序:jvm再分配内存时不是完全按照类定义的字段顺序去分配,而是根据jvm选项:-XX:FieldsAllocationStyle
(默认是1)来进行排序。对于所有的-XX:FieldsAllocationStyle
配置选项来说都会遵守以下两个原则:
- 如果一个字段的大小为S字节,则对象的开始位置到该字段的偏移量一定满足:该字段的位置 - 对象开始位置 = nS(n >= 1)为整数,即S的n倍。在64位jvm开启压缩指针的基础上举个例子:
对象的分配第一个字段是long数据,这时对象头的大小为12个字节,long的大小8个字节。12不能整除8,这时jvm会填充4个字节,让字段从16开始。如下图:
- 子类继承父类字段的偏移量一定和父类是一致的。在64位的jvm实现中会对子类的实例数据进行如下对齐:如果开启了压缩指针则子类的第一个字段的偏移量是4N,关闭压缩指针之后是8N。
a.验证子类继承父类字段的偏移量一定和父类一致的:
看到子类B中字段i的偏移量上面父类A中i的偏移量是一致的,都在16位置。而且子类B的第一个字段j的偏移量正好满足"4N"。
b.验证关闭压缩指针-XX:-UseCompressedOops之后,子类的第一个字段偏移量是8N:
如图,子类B的第一个字段偏移量是被填充到24,红色箭头所指的填充块;关闭压缩指针之后子类的第一个字段偏移量是8N。
padding 填充区
padding填充区和instance data区的填充区是有区别的。padding填充区是类级别的填充,专指对齐填充对象末尾的,所有(loss due to the next object alignment)标识的地方,下图红色区域;而instance data区的填充区是全局变量级别的,是为了对齐全局变量,下图蓝色区域。
填充规则:如果对象填充完实例数据后的大小不满足"8N",则填充到8N。
总结:对象是由三部分组成:对象头,实例数据,对齐填充。
对象头包括Mark Word和 Class Pointer。Mark Word包括对象hashCode,GC信息和锁信息。在32位JVM下对象头大小为8字节。64位16字节,开启压缩指针后为12字节,压缩的是Class Pointer,压缩为4。
实例数据也会有对齐填充。
对象大小一定满足8N。