JVM——对象的创建

1. 引言

复习一下常见的创建对象的方法有:new,反射,克隆,反序列化,对于普通的new创建对象而言,在虚拟机的层次上又是如何实现的呢。

2. 对象的创建

2.1 初始化前的检查

虚拟机遇到new指令首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。
接下来虚拟机将为新生对象从堆中分配内存,分配内存的方法有两种

  • 指针碰撞(Pump the Pointer)
  • 空闲列表(Free List)

2.2 分配内存

选在哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,采用空闲列表。在创建对象的过程中需要考虑到指针线程安全问题,有两种方案

  • CAS配上失败重试方式,保证操作的原子性
  • 为每个线程在Java堆中预分配一小块内存(Thread Local Allocation Buffer,TLAB)

2.3 虚拟机初始化对象

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零(不包括对象头),这一步保证了对象实例字段在Java代码中可以不赋初始值就直接使用。虚拟机会对对象进行必要的设置,例如对象时哪个类的实例、如何才能得到类的元数据信息,对象的哈希码、对象的GC分代年龄等信息。这样从虚拟机的角度来讲一个新的对象就生成了,但是从程序员的角度来讲对象的初始化还没有开始呢。

3. 对象的内存布局

对象在内存中存储的布局可以分为3块区域:对象头(Header)、实力数据(Instance Data)、对齐填充(Padding)。
HotSPot虚拟机的对象头包括两部分信息,第一部分存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
另一部分是类型指针,指向类元数据的指针,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。

4. 对象的访问定位

java程序需要通过上的reference数据来操作上的具体对象,目前主流的访问方式有

  • 使用句柄
  • 直接指针

4.1 使用句柄

Java堆中划分出一块内存作为句柄池,reference存储的是句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。

JVM——对象的创建

4.2 直接指针

reference存储的指针直接指向堆中的对象,此时对象实例数据需要考虑如何放置访问类型数据的相关信息

JVM——对象的创建
两种方式各有优势,句柄的优势是在对象被移动后只需要改变句柄的实例数据指针就行,reference本身不需要修改。直接指针的方式节省了一次指针定位时间,这样更快(HotSpot使用)。