概述
通常情况下,我们创建一个对象,只需要使用new关键字即可。而对于java虚拟机来说,需要经历一系列过程。 首先,需要找到对应的类是哪个,这个类是否已经加载,没有加载还需要将它先加载进来,然后给将要创建的对象分配内存,然后对对象进行初始化设置,我们才能使用一个完整的对象。
查找类
分配内存
经过查找类的一系列操作之后,java虚拟机知道了创建哪个类的对象,这时候需要内存来放置将要创建的对象。所以接下来需要分配内存,一个类加载进来之后,对象所需要的内存已经确定,现在需要找到一块空闲区域划分给将要产生的对象。
分配方式
指针碰撞方式
这种方式需要将使用过的内存放在一边,未使用的放在另一边,指针指向第一个空闲位置,这时候分配内存只需要将指针往空闲位置移动对象大小位置即可。但是我们知道,内存有分配和回收,当不断的进行分配和回收之后,内存就不是连续的,如果我们使用这种方式显然行不通,所以我们需要采用的垃圾处理器带有空间压缩整理的功能,例如:Serial,ParNew
空闲列表方式
还有一种方式,空闲列表方式,根据名称我们都知道,用一个列表记录所有的空闲块,这个需要单独维护一个列表。CMS这种基于清除算法的收集器,就是采用了这个方式。但是有进行进一步的优化,为了能在大多数情况下分配的更快,设计了一个分配缓冲区,从空闲列表拿下一块较大的内存,然后在这块内存内部采用指针碰撞方式来进行分配
并发问题
对象创建在虚拟机中是非常频繁的行为,即使使用最简单的指针碰撞方式,仅仅修改指针所指向的位置,在并发情况下也不是线程安全的(如果正在给对象A分配内存,还没修改指针位置,对象B也需要分配内存,相当于使用错误的指针位置)。
同步处理
一种处理方式是进行同步处理,实际上虚拟机采用CAS+失败重试的方式保证更新操作的原子性。当对象A正在分配内存时,标识正在进行分配,对象B来时发现正在分配,则分配失败,继续重试,请求分配,直到对象A分配完毕,对象B继续进行分配。
空间隔离
另一种方式是把内存分配的动作按照线程划分在不同的空间中进行,即给每一个线程预先分配一小块内存,称为本地线程分配缓冲(Tread Local Allocation Buffer TLAB),哪个线程需要分配内存时候,现在自己的本地缓冲区中进行分配,不够了再进行同步锁定方式。虚拟机是否使用TLAB可以通过- XX:+/-useTLAB参数进行设定。
初始化
当给对象分配了空间之后,我们需要对对象进行初始化操作,初始化对象的有三个步骤
预初始化
将除了对象头以外的所有内存空间初始化为零值(如果使用了TLAB,这一步操作可以提前到TLAB分配是进行),这步操作保证了对象的实例字段在java代码中可以不赋值直接使用。
设置对象头
对象头,既然位于最前面,应该存储一些描述对象的基本信息,其中包括该对象是哪个类的实例,如何找到类的元信息,对象的hash码(实际不是在这里进行设置,而是在调用Object::hashCode()时计算),对象的GC分代年龄,锁等信息。到这一步,在虚拟机层面一个新的对象已经产生了。
初始化
最后一步,才是按照开发人员真正的意愿去构造对象需要的其他资源(父类等)和状态信息,这样一个真正可用的对象才构建出来。
总结
对象的创建过程很简单,就是找到创建依据(类),放在哪里(分配内存),初始化(设置我们想要的东西)。