目录
对象的实例化
创建对象的几种方式
- new
- 最常见的方式
- 变形1:Xxx的静态方法
- 变形2:XxxBuilder/XxxFactory的静态方法
- Class的newInstance():反射方式,只能调用空参的构造器,权限必须是public
- Constructro的newInstance(Xxx):反射的方式,可以调用空参、两参的构造器,权限没有要求
- 使用clone():不调用任何构造器,当前类需要实现Cloneable接口,实现clone()
- 使用反序列化:从文件中、从网站中获取一个兑现的二进制流
- 第三方库Objenesis
从字节码角度看对象的创建过程
举个栗子:
public class ObjectTest {
public static void main(String[] args) {
Object obj = new Object();
}
}
从字节码角度看:
new操作符在做什么呢?
答:
- 到字符串常量池找到#2,对应到方法区中看,是不是已经把Object类进行加载了?如果没有加载就需要使用类的加载器把java.lang包下的Object加载一下。
- 完了之后,先在方法区把Object类加载过来。new操作符还会在堆空间中开辟一个Object对象的空间(Double Lang占8个字节,其他占4个字节)并进行临值初始化(double默认值0.0.boolean默认0.int默认0)。
以上就是new要做的事情。
dup:就是复制,在栈空间中的操作数栈,把当前生成的变量的引用复制一份,这样就有两个引用指向堆空间的实体,为什么有两个呢?栈底的作为赋值操作,上面的作为一个句柄去调用相关的方法。
invokespecial:构造器相关,把形参压入操作数栈,以及对堆空间的实例进行显示初始化等!
astore:把当前变量从操作数栈取出放到对应的局部变量表中。
对象创建的六个步骤
前面所述是从字节码角度看待对象的创建过程,接下来从执行步骤角度来分析。
一共有六个步骤:
- 判断对象对应的类是否加载、链接、初始化
- 为对象分配内存
- 如果内存规整——指针碰撞
- 如果内存不规整——虚拟机需要维护一个列表,空闲列表分配
- 处理并发安全问题
- 采用CAS配上失败重试保证更新的原子性
- 每个线程预先分配一块TLAB
- 初始化分配到的空间——所有属性设置默认值,保证对象实例字段在不赋值时可以直接使用
- 设置对象的对象头
- 执行init方法进行初始化
第一步:判断对象对应的类是否加载、链接、初始化
虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化( 即判断类元信息是否存在)。如果没有,那么在双亲委派模式下,使用当前类加载器以ClassLoader+包名+类名为Key进行查找对应的.class文件。如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class类对象。
当加载完类之后,对象在堆空间占用多大的内存大小已经确定下来了。