一、创建对象的方式

1、new

(1)直接new 对象

(2)单例模式,构造器是私有的,通过静态方法获取对象

(3)工厂类产生对象

2、class的newInstance()

反射的方式,只能调用空参构造器,权限是public

3、Constructor的newInstance()

可以调用有参构造器,权限没有要求

4、使用clone()

实现Cloneable接口,浅拷贝不使用构造器,深拷贝需要用到其他方式产生对象

5、使用反序列化

从文件中,网络中获取一个二进制流

6、第三方库

Objenesis

二、对象字节码

ios ObjectC 实例化delegate_创建对象

三、创建对象的步骤

1、判断对象的对应的类是否加载,链接,初始化

虚拟机遇到new指令,首先检查这个指令的参数能否在元空间的常量池中定位到一个类的符号引用,并且检查这个类的符号引用代表的类已经被加载,解析和初始化。如果没有,则使用双亲委派机制加载,以当前类加载器+包名+类名为key进行查找对应的.class文件。如果没有找到文件,则抛出ClassNotFoundException异常。如果找到则进行类加载,生成对应的class类对象。

2、为对象分配内存

首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象,如果实例成员变量是引用变量,进分配变量空间即可,即4个字节。

对象所需内存大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。

按照堆内存是否规整可以划分为以下两种分配方式:

(1) 指针碰撞

如果java堆内存是规整的,所用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配的内存是指针向空闲区域挪动一段与对象大小相等的距离。

(2)空闲列表

如果java堆内存是不规整,虚拟机需要维护一个列表,记录哪些块可以用,在分配的时候从列表中找到一块足够大

的空间分配给对象,并更新列表上的记录。

小结:

选择哪种分配方式是由java堆是否规整决定,而java堆是否规整又由采用的垃圾收集器是否带有压缩整理功能决定的。

使用Serial,ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

3、处理并发问题

在虚拟机创建对象是非常频繁的行为,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。

解决此种并发问题,jvm提供两种方案:

(1) 对分配内存空间的动作进行同步处理,实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性

(2) 内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程的TLAB上分配,只有TLAB用完分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB可以通过-XX:+/-UseTLAB参数设定。
4、初始化分配到的空间

所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用。内存分配完成后,虚拟机需要将分配到的内存空间都初始化为0(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步保证了对象的实例字段在java代码中不赋初始值就可直接使用。

5、设置对象头

虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头之中。根据虚拟机当前的运行状态的不同,如是否启用偏向锁,对象头会有不同的设置方式。

6、执行init方法进行初始化

在程序的角度看,初始化才正式开始,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

到此为止,虚拟机创建对象完成。