1.对象的创建
虚拟机创建一个Java对象大致的流程如下:
1.1类加载检查
当虚拟机遇到一条new指令的时候,首先要去检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。
1.2给对象分配内存
对象所需内存大小在类加载完成后便可确定。如果Java堆中的内存是规整的,就只需要把指针向空闲空间那边挪动一段于对象大小相等的距离。这种分配方式称为指针碰撞。如果Java堆中的内存不是规整的,在分配的时候要从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式叫做“空闲列表”。这个列表记录了哪些内存块是可以用的。
1.3初始化内存空间
内存分配完成之后,虚拟机需要将分配到的内存空间都初始化成零值(不包括对象头)。这一操作保证了对象实例字段在Java代码中可以不赋初值就可以直接使用。
1.4设置对象
虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄信息。这些信息放在对象的对象头中。
1.5产生新对象
从虚拟机视角来看,一个新的对象已经产生了。但从Java程序的视角来看,对象创建才刚刚开始——方法还没有执行,所有的字段都还未零。
所以一般来说,执行new指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
2.对象内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3快区域:对象头、实例数据和对齐填充。
2.1对象头
对象头包含两部分信息。
第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据被称为“Mark Word”。Mark Word是一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。
对象头的另一部分是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。不过,查找对象的元数据信息并不一定要经过对象本身。如果对象是一个Java数组,那么在对象头中还必须有一块用于记录数组长度的数据。
2.2实例数据
实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
2.3对齐填充
对齐填充不是必然的,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍。对象头部分正好是8字节的倍数,因此,当对象的实例数据部分没有对齐时,就需要通过对齐填充来补全。
3.对象的访问定位
目前主流的访问方式有使用句柄和直接指针两种。
3.1句柄访问
Java堆中将会划分一块内存来作为句柄池,references中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
3.2直接指针访问
Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而references中存储的直接就是对象地址。
3.3两种访问方式对比
使用句柄来访问的最大好处就是references中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据的指针,而references本身不需要修改。
使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。