Java是一门面向对象的语言,创建对象通常仅仅是一个new
关键字而已。而在虚拟机中对象的创建过程是怎么样的呢?
1.检查类加载
当Java虚拟机遇到一条字节码new
指令时,首先会去检查这个指令的参数。检查的目标有:
- 参数是否能够在常量池中定位到一个类的符号引用
这个符号引用所代表的类是否已经被加载、解析和初始化(类加载的加载过程)
如果没有,就必须先执行相应的类加载过程
加载
- 获取类的二进制字节流
- 静态存储结构转化为运行时数据结构
- 内存中生成一个数据访问入口
验证
- 验证class文件是否满足约束要求
准备
- 为静态变量分配内存并设置初始值
解析
- 符号引用替换为直接饮用
初始化
- 执行
<clinit>()
方法
- 执行
2.分配内存
虚拟机为新生对象分配内存,主要分配方式有两种
指针碰撞分配方式
- 维护一个指针做指示器,一边是满的另一边是空的用于分配。分配内存就把指针向空闲方向移动一段与对象相等的距离
- 标记整理、复制相关GC收集策略用(Serial等)
空闲列表分配方式
- 维护一个列表,记录可用的内存块,在分配的时候找到一个足够大的空间划分给对象实例并更新列表上的记录
- 标记清除策略的GC用(CMS等)
对象创建在虚拟机中非常频繁,修改指针的行为在并发情况下不是安全的,此时使用TLAB来进行优化
把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配了一块TLAB(本地线程分配缓冲),只有自己的TLAB用完了,分配新的缓存区才需要同步锁定。
3.初始化内存空间
内存分配完成之后,虚拟机需要把分配到的内存空间都初始化为零值。如果使用了TLAB的话,这一项工作也可以提前到TLAB分配时顺便进行。
这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用(访问到这些字段的数据类型对应的零值)
4.设置对象头中的数据
设置对象的相关信息,包括:
- 这个对象是哪个类的实例
- 如何才能找到类的元数据信息
- 对象的哈希码
- 对象的GC分代信息
- 等等。。
5.运行构造函数
前面4步工作完成后,从虚拟机的角度来看的话一个新的对象已经产生了。但是从Java程序的角度来看,对象创建才刚刚开始,因为Class文件中的<init>()
方法还没有执行,所有的字段都为默认的零值。
一般来说,new
指令之后会接着执行<init>()
方法,按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算完全被构造出来。
对应的,类的初始化使用的是<clinit>()
方法,对象使用的是<init()>
方法,不能弄混了