对象的组成
根据java虚拟机规范里面的描述:java对象分为三部分:对象头(Object Header), 实例数据(instance data),对齐填充(padding)。如图:
数组与对象类似,只是对象头部分多了数组长度Length的存储长度为4字节。
对象头(Object Header):
从图片上得知对象头分为两部分: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字节。
下面是用一张图展示 32位JVM上对象头的内存分布,方便理解。
实例数据(instance data):
有一种错觉就是实例数据里面通常只存了数据本身,但实际上这部分JVM也做了对齐填充,这样做的目的是为了内存对齐,为了实现这个效果JVM会执行一个字段重排列的操作。
字段重排列: 故名思义就是JVM在分配内存时不一定是完全按照类定义的字段顺序去分配,而是根据JVM选项-XX:FieldsAllocationStyle(默认是1)来进行排序。对于所有的
-XX:FieldsAllocationStyle配置选项来说都会遵守以下两个原则:
1、如果一个字段的大小为S字节,则对象开始的位置到该字段的位置的偏移量一定满足:
该字段的位置 - 对象开始位置 = mS (m >=1) 即是S的m倍。
在64位JVM开启压缩指针的基础上举个例子:
对象的分配第一个字段是个long 类型,这时对 象头的大小为12个字节,long的大小8个字节。14不能整除8,这时JVM会填充4个字节。让字段的位置从16开始,这样压缩节省出来的4字节空间又还回去了。。。。
2、子类继承父类字段的偏移量一定和父类是一致的。在具体的64位JVM实现中对还会对比子类的实例数据进行以下对齐:如果开启了压缩指针则子类第一个字段的偏移量是4N,关闭压缩指针之后是8N。
JOL 工具来验证上面的结论
1.ms验证
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
/**
* java 对象内存分布
*/
class A{
long i;
}
public class MemoryLayoutTest {
public static void main(String[] args){
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseClass(A.class).toPrintable());
}
}
得到的结果
memory.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (alignment/padding gap)
16 8 long A.i N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
为了满足mS,进行了4个字节的填充(16 - 0 = 2*8)
2.偏移量是一致
class A{
long i;
}
class B extends A{
int j;
}
public class MemoryLayoutTest {
public static void main(String[] args){
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseClass(B.class).toPrintable());
}
}
memory.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (alignment/padding gap)
16 8 long A.i N/A
24 4 int B.j N/A
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 4 bytes internal + 4 bytes external = 8 bytes total
对齐填充:
这里的对齐填充专指对象末尾的填充,如果对象填充完实例数据后的大小不满足"8N",则填充到8N,其实在上面运行结果中就已经有提示了。
上面大小到28时满足不了8N,故填充了4字节,使得实例数据大小达到32字节,满足了8N。
总结:
java对象由三部分组成:对象头,实例数据,对齐填充。
对象头包括Mark Word和 Class Pointer。Mark Word包括对象hashCode,GC信息和锁信息。在32位JVM下对象头大小为8字节。64位16字节,开启压缩指针后为12字节,压缩的是Class Pointer。
实例数据也会有对齐填充。(实例数据填充)
对象大小一定满足8N。(对象填充)