对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头、实例数据、对齐填充。
对象头
HotSpot虚拟机中的对象头包括两部分内容:
第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID、偏向时间戳等,因为对象的运行时数据很多,所以存储的结构并不固定。
第二部分是类型指针,即对象执行她类元数据的指针,虚拟机通过这个指针来确定对象属于哪个类的实例。(并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的类元数据并不一定通过对象本身)。如果对象是一个数组,那么在对象头中还要存储数组的长度。
实例数据
实例数据部分是对象真正储存的有效信息,也就是在程序代码中所定义的各种类型的字段内容,无论是从父类继承来的,还是在子类定义的,都需要记录起来。存储顺序受虚拟机分配策略参数所影响,HotSpot虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、ops,从分配策略可以看出,相同宽度的字段总是在一起,在满足这个条件的情况下,父类中定义的元素会出现在子类之前
对齐填充
第三部分的对齐填充数据不是必须出现的,也没有特别的含义,他仅仅起着占位符的作用(由于Hotspot VM的自动内存管理喜用要求对象的起始地址必须是8字节的整数倍,就是对象的大小不惜是8字节的整数倍,所以就需要对齐填充数据了)
关于对象大小的计算
引用自 点我
32 位系统下,当使用 new Object() 时,JVM 将会分配 8(Mark Word+类型指针) 字节的空间,128 个 Object 对象将占用 1KB 的空间。如果是 new Integer(),那么对象里还有一个 int 值,其占用 4 字节,这个对象也就是 8+4=12 字节,对齐后,该对象就是 16 字节。以上只是一些简单的对象,那么对象的内部属性是怎么排布的?
Class A {
int i; byte b; String str; }
其中对象头部占用 ‘Mark Word’4 + ‘类型指针’4 = 8 字节;byte 8 位长,占用 1 字节;int 32 位长,占用 4 字节;String 只有引用,占用 4 字节;那么对象 A 一共占用了 8+1+4+4=17 字节,按照 8 字节对齐原则,对象大小也就是 24 字节。这个计算看起来是没有问题的,对象的大小也确实是 24 字节,但是对齐(padding)的位置并不对:在 HotSpot VM 中,对象排布时,间隙是在 4 字节基础上的(在 32 位和 64 位压缩模式下),上述例子中,int 后面的 byte,空隙只剩下 3 字节,接下来的 String 对象引用需要 4 字节来存放,因此 byte 和对象引用之间就会有 3 字节对齐,对象引用排布后,最后会有 4 字节对齐,因此结果上依然是 7 字节对齐, 此时对象的结构示意图,如下图所示:
对象的访问定位
我们的java程序需要通过栈上的reference数据来操作堆上的具体对象,而对象的访问方式由虚拟机实现来决定,目前主流的访问方式有句柄和直接指针两种。
句柄
使用句柄的方式,java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄池地址,而句柄池中包含了对象实例数据与类型数据各自的具体地址信息。
直接指针
使用直接指针访问,reference存储的就是对象的地址,而java堆中的对象不仅保存中对象的实例数据,还引用了对象的类型数据。
两种方式各有优势:
1。句柄的方式最大的好处就是存储的是句柄的地址,当对象移动时不需要改变引用地址(垃圾回收时对象移动是很频繁的事情),只改变句柄中实例数据的指针,但这需要频繁的移动指针。
2.直接指针的方式最大的好处就是速度更快,她节省了一次指针定位的时间开销,由于对象的访问很频繁的事情,积少成多也可以节约许多时间成本。
而对于虚拟机sun Hotspot而言,他使用的是直接指针的方式来进行对象访问的。
一篇关于JVM内存分配模型的概念讲的很好的文章:内存模型
当它本可进取时,却故作谦卑;
当它在空虚时,用爱欲来填充;
在困难和容易之间,它选择了容易;
它犯了错,却借由别人也会犯错来宽慰自己;
它自由软弱,却把它认为是生命的坚韧;
当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;
它侧身于生活的污泥中,虽不甘心,却又畏首畏尾。