当虚拟机遇到一条 字节码new指令时,
-
检查这个指令的参数是否在常量池中定位到一个类的符号引用;
-
并检查这个符号引用代表的类是否已被 加载、解析、初始化过,如过没有,必须执行相应的类加载过程;
-
在类加载检查通过之后,虚拟机将会为新生对象分配内存
-
对象所需的内存在类加载后就可以完全确定;
-
为对象分配内存其实就是在堆上划分出一个确定大小的内存
-
有两种分配内存的方式:
-
“指针碰撞”:堆内存规整,垃圾收集器带有空间压缩整理能力(Serial、ParNew)
-
“空闲列表“:堆内存不规整,垃圾收集器不带空间压缩整理能力(CMS 基于”清除swap“算法)
-
虚拟机创建对象内存分配时的线程安全问题的解决:
-
对分配内存空间的动作进行同步处理。实际上,虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;
-
把内存分配的动作按照线程划分到不同的空间中进行。即每个线程在java堆中预先一小块内存,称为本地线程分配缓存(Thread Local Allocation Buffer,TLAB)分配内存时在该线程的本地缓冲区分配,只有本地缓冲区用完了,分配新的缓冲区时才需要同步锁定。虚拟机是否使用TLAB,可以通过 -XX:+/-UseTLAB参数来设定。
-
-
内存分配完成后,虚拟机必须将分配到的内存空间(不包括对象头)都初始化为零值。如果使用了TLAB,这个步骤可以提前至TLAB分配时顺便进行。这个步骤保证了对象的实例字段可以不赋初值就直接使用。
-
接下来,虚拟机需要对对象进行必要的设置,例如,这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码(实际上对象的哈希码会延后到真正调用Object::hashCode()方法是时才计算)、对象的GC分代年龄等信息。这些信息存放在对象头(Object Header)中。
-
上述工作完成之后,Class文件的<init>()方法还没有执行,所有的字段都是默认值。new之后会接着执行<init>()方法按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算被构造出来。
总结:new一个对象包括:类是否加载、为对象分配内存、并发处理、内存空间初始化、对象设置、<init>()等步骤