首先以一个问题引出这次的内容:Object o = new Obeject()在内存中占了多少个字节?
要回答这个问题就要首先清楚对象在内存中的内存布局。

对象内存布局

根据java虚拟机规范里面的描述:java对象分为三部分:对象头(Object Header) = mark word + class point(对象的引用),实例数据(instance data,成员变量),对齐填充(padding)。如图:

java类内存布局 java 对象内存布局_偏移量


数组与对象类似,只是对象头部分多了数组长度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各占一半空间大小

java类内存布局 java 对象内存布局_偏移量_02

注意:在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配置选项来说都会遵守以下两个原则:

  1. 如果一个字段的大小为S字节,则对象的开始位置到该字段的偏移量一定满足:该字段的位置 - 对象开始位置 = nS(n >= 1)为整数,即S的n倍。在64位jvm开启压缩指针的基础上举个例子:

对象的分配第一个字段是long数据,这时对象头的大小为12个字节,long的大小8个字节。12不能整除8,这时jvm会填充4个字节,让字段从16开始。如下图:

java类内存布局 java 对象内存布局_java类内存布局_03

  1. 子类继承父类字段的偏移量一定和父类是一致的。在64位的jvm实现中会对子类的实例数据进行如下对齐:如果开启了压缩指针则子类的第一个字段的偏移量是4N,关闭压缩指针之后是8N。

a.验证子类继承父类字段的偏移量一定和父类一致的:

java类内存布局 java 对象内存布局_偏移量_04


看到子类B中字段i的偏移量上面父类A中i的偏移量是一致的,都在16位置。而且子类B的第一个字段j的偏移量正好满足"4N"。


b.验证关闭压缩指针-XX:-UseCompressedOops之后,子类的第一个字段偏移量是8N:

java类内存布局 java 对象内存布局_java_05


如图,子类B的第一个字段偏移量是被填充到24,红色箭头所指的填充块;关闭压缩指针之后子类的第一个字段偏移量是8N。

padding 填充区

padding填充区和instance data区的填充区是有区别的。padding填充区是类级别的填充,专指对齐填充对象末尾的,所有(loss due to the next object alignment)标识的地方,下图红色区域;而instance data区的填充区是全局变量级别的,是为了对齐全局变量,下图蓝色区域。

填充规则:如果对象填充完实例数据后的大小不满足"8N",则填充到8N

java类内存布局 java 对象内存布局_字段_06

总结:对象是由三部分组成:对象头,实例数据,对齐填充。

对象头包括Mark Word和 Class Pointer。Mark Word包括对象hashCode,GC信息和锁信息。在32位JVM下对象头大小为8字节。64位16字节,开启压缩指针后为12字节,压缩的是Class Pointer,压缩为4。
实例数据也会有对齐填充。
对象大小一定满足8N。