这个问题有坑,有两种回答
第一种解释:object实例对象,占16个字节。
第二种解释:Object o:普通对象指针(ordinary object pointer),占4个字节。
new Object():object实例对象,占16个字节。
所以一共占:4+16=20个字节。
第二种解释就是在玩文字游戏了,但还是要知道的。
二、这个答案适用于所有情况吗并不是,这个答案只适用于现在一般默认情况。
准确的说,只适用于Hotspot实现的64位虚拟机,默认开启了压缩类指针和压缩普通对象指针的情况下。
本文下述内容若无特殊说明,指的都是JDK8 Hotspot实现的64位虚拟机的未开启压缩的情况。
三、前置知识在 JVM 中,Java对象保存在堆中时,由以下三部分组成:
对象头(Object Header):包括关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。由两个词mark word和klass pointer组成,如果是数组对象的话,还会有一个length field。
mark word:通常是一组位域,用于存储对象自身的运行时数据,如hashCode、GC分代年龄、锁同步信息等等。占用64个比特,8个字节。
klass pointer:类指针,是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。占用64个比特,8个字节。开启压缩类指针后,占用32个比特,4个字节。
实例数据(Instance Data):存储了代码中定义的各种字段的内容,包括从父类继承下来的字段和子类中定义的字段。如果对象无属性字段,则这里就不会有数据。根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等。为了提高存储空间的利用率,这部分数据的存储顺序会受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。
对齐填充(Padding):对象可以有对齐数据也可以没有。默认情况下,Java虚拟机堆中对象的起始地址需要对齐至8的整数倍。如果一个对象的对象头和实例数据占用的总大小不到8字节的整数倍,则以此来填充对象大小至8字节的整数倍。
为什么要对齐填充?字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中。如果字段不是对齐的,那么就有可能出现跨缓存行的字段。也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行。这两种情况对程序的执行效率而言都是不利的。其实对其填充的最终目的是为了计算机高效寻址。
我看到网络上有些文章把mark word称之为对象头,把java对象的内存布局分为4个部分mark word、klass pointer、instance data、padding,这很明显是没有看过官方文档的,说法并不严谨。关于对象头,可以在Hotspot官方文档找到下面的描述:
四、详细解释因为第二种解释包含了第一种解释,所以我们分析第二种解释。
1.Object o在Hotspot实现的64位虚拟机中,原本情况下,它内部的一个引用,就应该占64个比特,也就是8个字节。什么叫引用啊?上面那个变量小o,就叫引用,也叫普通对象指针(别说什么java里没有指针,什么引用和指针不一样。我不想去争论这个)。但是,在第二种解释中我们说了,普通对象指针,占4个字节,怎么又成8个字节了,怎么回事呢?
这是因为Hotspot实现的64位虚拟机,默认会开启压缩普通对象指针,会把8个字节的对象引用,压缩成4个字节。
Object o占用大小分为两种情况:
未开启压缩对象指针
8字节
开启压缩对象指针(默认是开启的)
4字节
2.new Object()同样的,在Hotspot实现的64位虚拟机中,原本情况下,类指针应该占64个比特,也就是8个字节。但因为Hotspot实现的64位虚拟机,默认会开启压缩类指针(和压缩对象指针不一样),而类指针就在Klass Pointer中存储着,所以会把Klass Pointer压缩成4个字节。
new Object()占用大小分为两种情况:
未开启压缩类指针
8字节(Mark Word) + 8字节(Klass Pointer) = 16字节
开启压缩类指针(默认是开启的)
8字节(Mark Word) + 4字节(Klass Pointer) + 4字节(Padding) = 16字节
五、验证光说不练假把式,实践出真知,上面的只是理论,我们来实际验证下,是不是真的是这样。
1.验证默认开启压缩