HotSpot中的对象

2019-02-09

1 对象创建过程

  1. 遇到new指令的时候,检查该指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个类是否已加载、解析、初始化过,如果没有必须先进行类的加载过程(这个之后再整理);
  2. 分配内存。加载类之后一个对象所需的内存大小就确定了;使用Serial、ParNew等收集器时,堆内存是整齐的,所以使用指针碰撞划分内存,即在空闲内存的分界点开始分配指定大小的内存空间;如果用CMS等给予Mark-Sweep算法的收集器时,堆内存不规整,所以使用空闲列表划分内存,即JVM维护了一个记录可用内存的表,从列表中找一块足够大小的内存空间用于分配,并更新列表记录。
  3. 对于多线程同时创建对象时,指针可能出错,可以使用两种解决方案:
    • 对分配内存空间的动作进行同步,采用CAS+失败重试的方式可以保证操作的原子性。
    • 把内存分配动作按照线程划分在不同的空间之中进行,即每个线程在java对中预先分配一小块内存成为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。当TLAB用完并分配新TLAB的时才需要同步锁定。
  4. 内存分配完成后,进行初始化零值(如果使用TLAB,可以在TLAB分配时进行);
  5. 虚拟机对对象进行设置,设置内容存放在对象头(Object Header);
  6. 执行方法。

2 对象的内存布局

对象在内存中存储的布局有3块区域:对象头实例数据对齐填充

2.1 对象头

  1. 存储对象自身运行时数据。如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称为”Mark Word“。
  2. 类型指针。指向它的类元数据指针,通过这个指针可以确定该对象是那个类的实例。(并不是所有的虚拟机实现都有这个)
  3. 如果对象是一个Java数组,那么对象头中必须有一块用来记录数组长度。

2.2 实例数据

这部分是对象真正存储的有效信息,也是代码中定义的各种字段的内容。存储的顺序受虚拟机分配策略字段在代码中定义的顺序共同影响。

2.3 对齐填充

占位符的作用,可有可无。

3 对象的访问定位

对象的访问定位有两种:句柄定位直接指针。HotSpot用的是后者。

  • 句柄定位:Java堆会画出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。 句柄
  • 直接指针访问:Java堆对象的布局就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。 直接

对比:使用直接指针速度快;使用句柄时reference指向稳定的句柄,对象被移动时改变的也只是句柄中实例数据的指针,而reference本身并不需要修改。

参考文献

图解JAVA对象的创建过程
JAVA对象创建的过程