对象的创建
本文中讨论的对象限于普通JAVA对象,不包括数组和Class对象。当虚拟机遇到一个new指令时,首先检查这个指令的参数是否可以在常量池中找到该类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析,和初始化过。如果没有则必须先执行相应的类加载过程。
内存分配
在类加载检查通过之后,虚拟机在堆中为新生对象分配内存。一般通过如下两种方式
指针碰撞(Bomb The Pointer)
假设堆内存是绝对规整的,已经用过的和未用过的各处于一边,中间放着以一个指针作为分界点的指示器,那么分配内存就是把指针向着空闲内存空间移动一段与对象大小相等的距离。
空闲列表
假设堆内存不是规整的,此时虚拟机维护了一张列表,上面记录了那些内存块是可用的,在分配时从列表中找到一块足够大的空间划分给对象示例并更新列表上的记录。
内存分配的线程安全
- CAS加上失败重试保证更新操作的原子性
- TLAB 本地线程分配缓冲(Thread Local Allcation Buffer):把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在JAVA堆中预先分配一小块内存,哪个线程需要分配内存就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时才需要同步锁定。虚拟机是否使用 TLAB 可以通过–XX:+/-UserTLAB
初始化零值
内存分配完成后,虚拟机需要将分配的内存空间都初始化为零值(不包含对象头),如果使用TLAB,则这一操作可以提前至TLAB分配时完成。初始化零值确保了对象的实例字段可以不赋初始值就可以直接使用,程序能访问到这些字段的数据类型锁对应的零值。
对象信息设置
主要是设置对象头信息
对象的内存布局
在hostspot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header),实例数据(Instance Data),和对齐填充(Padding)。
对象头信息
HotSpot对象头包括两部分信息
- 对象自身的运行时数据 :如哈希码(HashCode)、GC分布年龄,锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在在32和64位虚拟机中(未开启指针压缩)分别为32bit 和64bit,官方称它为Mark World。Mark World 被设计成一个非固定数据结构以便于在极小的空间内存储更多信息,它会根据对象的状态复用自己的存储空间。例如在32位的虚拟机中,如果对象处于未锁定的状态下,Mark World的 32bit空间中 25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储标志位,1bit固定为0,而其他状态(轻量级锁定,重量级锁定,GC标记,可偏向)下对象的存储内容如下表:我
HotSpot虚拟机对象头Mark World
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码、对象分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标志 |
偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 |
- 类型指针,即对象指向它的类元数据的指针。虚拟机通过这个指针来确定是哪个类的实例(普通Java对象)。数组对象在对象头中还必须要有一块用于记录数组长度的数据。
实例数据
实例数据部分才是对象真正存储的有效信息,也是程序代码中锁定义的各种类型字段,无论是从父类中继承下来的还是在子类中定义的。这部分 的存储顺序会受到虚拟机分配策略参数(FieldAllocationStyle)和字段在Java源代码中定义顺序的影响。虚拟机默认的分配策略为long/doubles、ints、shorts/chars、boolean、oop(Orindary Object Pointer)。从分配策略中可以小看出相同宽度的字段总是被分配发到一起,在满足这个这个前提条件下,父类中定义的变量会出现在子类之前。如果CompactField参数值为true(默认为ture),那么子类中较窄的变量也会可能插入到父类变量的空隙中。
对齐填充
作为占位符。由于HotSpot的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍,而对象头部分正好是8字节的倍,因此若对象实例数据没有对齐时,就需要通过对齐填充来补全。
对象的访问定位
建立对象是为了使用对象,Java程序只需要通过栈上的reference数据来操作堆上的具体对象。
通过句柄访问对象
如果使用句柄访问对象,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄信息,而句柄中包含了对象的实例数据与类型数据各自的地址信息。
- 通过直接指针进行访问,堆中的对象布局需要考虑如放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
对比
- 通过句柄访问,reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。
- 使用直接指针访问,速度更快,节省了一次指针定位的时间开销。Sun HotSpot虚拟机采用的也是直接指针访问。那行
init
调用init方法进行初始化