Java创建对象的方式和过程
Java创建对象的几种方式
- 使用new关键字
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Test1 t2 = new Test1();
}
}
- 反射创建对象
public class Test1 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Test1 t1 = Test1.class.newInstance();
}
}
- clone实现
通过Clone创建对象,首先实体类中必须先实现Cloneable接口并复写Object的clone方法(因为Object的这个方法是protected的) - 反序列化
序列化:指把 Java 对象转换为字节序列的过程;
反序列化:指把字节序列恢复为 Java 对象的过程;
此方式需要类先实现Serializable接口
创建对象的过程
- 检查类是否已经被加载
- 去常量池中查找该引用所指向的类有没有被虚拟机加载,如果没有被加载,那么会进行类的加载过程。类的加载过程需要经历:加载、链接、初始化三个阶段。对象的大小,在类加载完成时确定。(jdk1.8中,运行时常量池、类常量池存在于方法区中。)
- 内存分配
- JVM为对象分配空间,即把一块确定大小的内存块从Java堆中划分出来。
- 初始化值
- 分配完内存后,JVM会对对象的所有实例变量进行默认初始化,将它们设置为默认值。例如,整型变量会被初始化为0,对象引用被初始化为null,布尔类型被初始化为false等。
- 执行构造函数
- 接下来,JVM会调用对象的构造函数来进一步初始化对象。构造函数可能会包含用户自定义的初始化逻辑,比如给实例变量赋值、执行某些操作等。
- 对象初始化完成
- 当构造函数执行完毕后,对象的初始化过程就完成了。此时,对象已经可以在程序中被正常使用。
- 返回对象引用
- 最后,new指令会返回一个指向新创建的对象的引用,这个引用通常会被存储在一个变量中,以便后续对对象的操作。
JVM为对象分配空间的方式(创建对象第二步)
指针碰撞
假设Java堆内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅把那个指针指向空闲方向挪动一段与对象大小相等的距离。
正常情况
给对象分配内存后
- 优点
这种方式的优点是工作简单,效率高,只需要移动指针就可以分配内存空间。 - 缺点
由于用指针碰撞分配内存空间分为两步:
- 读取指针当前的位置。
- 根据自身大小移动指针,不是原子操作,对象创建在虚拟机中是非常频繁的操作,在并发情况下,会导致执行读操作或执行写操作的结果与预设的结果不一致(指针划分不一致)。
例如:线程A要给对象分配8kb,读取到指针当前的位置,时间片用完,切换到线程B,线程B要给它的对象分配16kb,也读取到指针当前的位置(和线程A读取到的一样),将指针向空闲内存方向移动16kb大小,线程B时间片用完,切换到线程A继续执行,由于线程A使用的指针位置还是之前读到的。(线程不安全问题)
针对指针碰撞线程不安全,有两种方案:
- 同步处理(加锁)分配内存空间行为
采用 CAS 分配重试的方式来保证更新操作的原子性
- 把内存分配行为按照线程,划分在不同的内存空间进行
1、即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定
2、虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。
空闲列表
如果Java堆中的内存并不是规整的, 已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
怎么选择分配方式
两种方式的选择由 Java 堆是否规整决定,Java 堆是否规整是由选择的垃圾收集器是否具有压缩整理能力决定的。
-
将内存空间初始化为零值
内存分配完成之后,虚拟机必须将分配到的内存空间(但不包括对象头)都初始化为零值。零值初始化意思就是对对象的字段赋0值,或者null值,这也就解释了为什么这些字段在不需要进程初始化时候就能直接使用。
如果使用了TLAB的话,这一项工作也可以提前至TLAB分配时顺便进行。 -
对对象进行必要的设置
例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。从虚拟机的视角来看,一个新的对象已经产生了。但是从Java程序的视角看来,对象创建才刚刚开始——构造函数,即Class文件中的()方法还没有执行,所有的字段都为默认的零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好。
-
执行实例的初始化方法init
init方法包含成员变量、构造代码块的初始化,按照声明的顺序执行,执行对象的构造
方法,并把堆内对象的首地址赋值给引用变量。至此,对象创建成功。