您的位置:首页 > 编程语言 > Java开发

对象的内存布局(java对象大小)

2016-09-04 10:43 323 查看
转载自:http://www.infoq.com/cn/articles/jvm-hotspot

              http://www.open-open.com/lib/view/open1423111722764.html
              http://blog.csdn.net/u013256816/article/details/51008443
              http://blog.csdn.net/lihuifeng/article/details/51681146
              http://www.open-open.com/lib/view/open1423111722764.html
       HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。 

       


 

对象头

       对象头包括两部分信息:Mark Word(标记字段)和Klass Pointer(类型指针),如果对象是数组类型,还将包括Array length(数组长度)。

       Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。这部分数据的长度为1个Word(字宽),在32位虚拟机中,一字宽等于四字节,即32bit,在64位的虚拟机(暂不考虑开启压缩指针的场景)中一字宽等于64个Bits。

       对象需要存储的运行时数据很多,其实已经超出了32、64位Bitmap结构所能记录的限度,但是对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0。各状态下(无锁状态、轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示(32-bit)。

       


       详细请参考:http://stackoverflow.com/questions/26357186/what-is-in-java-object-header ,http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/oops/markOop.hpp

       Klass Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。其占用的空间也是1个Word,64位的虚拟机中占8个字节,开启压缩指针后占4字节。

       另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。64位的虚拟机中占8个字节,开启压缩指针后占4字节。

实例数据

       实例数据部分是对象真正存储的有效信息,也既是在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的都需要记录下来。

       这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。HotSpot虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果CompactFields参数值为true(默认为true),那子类之中较窄的变量也可能会插入到父类变量的空隙之中。 

     原生类型(primitive type)的内存占用如下:
boolean 1
byte    1
short   2
char    2
int     4
float   4
long    8
double  8
reference类型在32位系统上每个占用4B;在64位系统上每个占用8bytes,开启指针压缩后占用4个字节。

对齐填充

       对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。注意对齐填充是以每个对象为单位进行的。对象头部分正好是8字节的倍数,因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。如下公式:

       (对象头 + 实例数据 + padding) % 8等于0且0 <= padding < 8

关于指针压缩(-XX:+UseCompressedOops)

       对象头在32位系统上占用8字节(4 + 4,不是数组类型),64位系统上占16字节(8 + 8)。 无论是32位系统还是64位系统,对象都采用8字节对齐。Java在64位模式下开启指针压缩,mark区域依旧是8字节,但kclass区域被压缩为4字节;如果没有开启指针压缩,mark和kclass都是8字节。

       例如:new char[3]:

          指针未压缩 =  (8 + 8 + 8) + 2 * 3 + 2 = 32B

          指针压缩 =  (8 + 4 + 4) + 2 * 3 + 2 = 24B

另一个示例:(参考:http://www.open-open.com/lib/view/open1423111722764.html)
static class B {
int a;
int b;
}
static class C {
int ba;
B[] as = new B[3];

C() {
for (int i = 0; i < as.length; i++) {
as[i] = new B();
}
}
}
       对象C在堆中内存格局大致如下:

       


       那么C对象占用的全部内存,主要是三部分构成:C对象本身的大小+数组对象的大小+B对象的大小。

          未开启压缩:(16 + 4 + 8+4(padding)) + (24 + 8 * 3)  + (16 + 8) *3 = 152bytes

          开启压缩:(12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(数组对象padding)) + (12+8+4(B对象padding))*3= 128bytes
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: