文章目录
对象的实例化
-
创建对象的方式
- new
- Class的newInstance :反射的方式,只能调用空参的构造器,必须是public
- Constructor的newInstance(xxx):反射的方式,可以调用有参数或者无参数构造器,权限没有要求。
- 使用clone。不调用任何构造器,当前类需要实现Cloneable接口,实现 clone()方法。
- 使用反序列化:从文件或者网络获取流
- 第三方库Objenesis
-
创建对象的步骤
- 判断对象对应的类是否加载、链接、初始化
- 为对象分配内存
- 处理并发安全问题
- 初始化分配到空间
- 设置对象的对象头
- 执行init方法进行初始化
-
下面看看简单new一个对象,从字节码角度看看创建对象的过程。
public static void main(java.lang.String[]);
// 参数 返回值Void
descriptor: ([Ljava/lang/String;)V
// 一些标志位
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
// 操作数栈深度2 本地变量表2哥 参数是1个
stack=2, locals=2, args_size=1
// new 指令 #2 对应查找常量池中的 java/lang/Object 方法,会先判断类是否已经加载,如果没加载会先进行类加载。然后开辟堆空间 然后初始化一些默认值
0: new #2 // class java/lang/Object
3: dup // 复制栈空间的操作数栈
4: invokespecial #1 // Method java/lang/Object."<init>":()V // 调用<init> 执行构造器 读取参数加入操作数栈 进行赋值等操作
7: astore_1 // 加入局部变量表
8: return // 返回
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 object Ljava/lang/Object;
}
- 从执行步骤来看创建对象的过程
1: 判断对象对应的类是否加载、链接、初始化
当虚拟机接收到new指令的时候,首先去检测Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经类加载解析和初始化(类元信息是否已经存在)。如果没有那么在双亲委派的模式下,使用当前类加载器进行查找对应的.class 文件。如果没有找到则抛出 ClassNotFoundException 异常。如果找到则进行类加载,并生成对应的Class类对象。
2:为对象分配内存
->如果内存规整:比如Serial、ParNew;垃圾回收算法采用的是标记压缩算法,所以内存规整连续,所以采用指针碰撞的方式,指针向后移动为对象分配空间。
->如果内存不规整:已使用的内存和未使用的内存相互交错,那么虚拟机采用的是空闲列表法为对象分配内存。(就是维护了一个列表,需要分配内存的时候去查列表哪里够就分配到哪里)(CMS采用的标记清除算法)
3: 处理并发安全问题:
a:采用CAS失败重试、区域加锁保证原子性。
b:为每个线程预先分配TLAB
4:初始化分配到空间:
所有属性设置默认值:保证对象实例字段在不赋值的情况下可以使用。
5:设置对象的对象头
将对象的所属类,对象的HashCode、对象的GC信息、锁信息、等数据存储在对象头。
6:执行init进行初始化
在Java程序的角度看来,此时初始化才正式开始,初始化成员变量、执行实例化代码、调用类的构造方法、并把堆对象的地址赋值给引用变量(一般来说看字节码是否有 invokespecial指令所决定)、这样一个真正的对象就创建出来了。