前言

这篇文章主要是介绍一下Java对象在虚拟机中是如何创建的?由什么组成?又是如何访问到的?文中讨论的只是普通的Java对象,不包括数组和Class对象等。

对象的创建

当虚拟机执行到一个new指令时,首先检查这个指令参数在常量池中是否定位到一个类的符号引用,并且检查这个类是否被加载、解析或初始化过,如果没有的话,那就要先执行相应类的加载过程。

类加载检查通过后,就需要在Java堆上为新对象分配内存空间,一般来说都是在堆的新生代分配,新对象所需的内存空间在类加载的时候就已经确定了,至于空间从什么地方开始分配,不同的虚拟机有不同的实现,常见的有一下两种:

  • 指针碰撞 :如果虚拟机的堆是规整的,空闲空间连续,那么会有一个指针记录当前内存分配到哪个位置,只需要将指针向后移动新对象所需的内存区域大小,就完成了对象的内存分配;
  • 空闲列表:如果Java堆空间规整的,空闲空间不连续,那么一般虚拟机会维护一个列表,记录堆中哪些是空闲的,哪些是使用的,在列表中找到适合新对象大小的内存区域来创建新的对象,这种方式就称为空闲列表。

除了对象内存空间的位置的选择的问题,对象的创建还面临这另外一个问题,那就是并发下的线程安全,就简单拿修改一个指针的引用来说,当正在给对象A分配空间,指针还没有来得及更改,其他线程来给对象B分配内存的情况,就会造成分配不正确的问题。

要解决上面的问题,有两种解决方案:

  • 同步处理:虚拟机利用的CAS加上失败重试的方式来保证空间分配的原子性;
  • 本地线程分配缓冲(LTAB):每一个线程预先在堆中设置一个本地线程分配缓冲,当线程需要空间时,先从本线程的缓冲区中分配,当缓冲区用完后需要分配新的缓冲区时,才加上同步锁定。

完成了对象内存的分配,还要对对象做一些必要的设置,比如说这个对象是哪个类的实例、对象的哈希码、对象的GC分代年龄等信息,这些信息一般存放在对象头中。

到现在,从虚拟机的角度来看,一个对象已经创建完成了,但是,就Java线程来说,对象创建才开始,init() 方法还没有执行,所以说接下来要执行init()方法,让对象按照程序员的意愿进行初始化,这样才完成了整个对象的创建过程。

总结

Java对象的创建过程可以分为以下几个步骤:

  1. 检查类是否加载;
  2. 堆空间的内存分配:分为指针碰撞和空闲列表两种方式;
  3. 必要信息的设置:对象头的一些信息;
  4. 对象的init()方法的执行,创建完成。