对象创建
-
类加载检查
遇到new指令时,先检查指令参数是否能在常量池中定位到一个类的符号引用:
- 如果能定位到,检查这个符号引用代表的类是否已被加载、解析和初始化过;
- 如果不能定位到,或没有检查到,就先执行相应的类加载过程;
-
为对象分配内存
对象所需内存的大小在类加载完成后便完全确定(JVM可以通过普通Java对象的类元数据信息确定对象大小),为对象分配内存相当于把一块确定大小的内存从Java堆里划分出来。
分配方式:
-
指针碰撞
如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;分配内存只需向空闲那边移动指针,这种分配方式称为”指针碰撞”(Bump the Pointer);
-
空闲列表
如果Java堆不是规整的:用过的和空闲的内存相互交错, 需要维护一个列表,记录哪些内存可用。 分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为”空闲列表”(Free List);
-
决定因素
Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的; 所以,使用Serial、ParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存;而使用CMS这种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式;
后面了解垃圾收集时应注意这里的内容。
-
-
线程安全问题
并发时,上面两种方式分配内存的操作都不是线程安全的,有两种解决方案:
-
同步处理
对分配内存的动作进行同步处理:
JVM采用CAS(Compare and Swap)机制加上失败重试的方式,保证更新操作的原子性;
CAS:有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做;
-
本地线程分配缓冲区
把分配内存的动作按照线程划分在不同的空间中进行:
在每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer,TLAB)。 哪个线程需要分配内存就从哪个线程的TLAB上分配,只有TLAB用完需要分配新的TLAB时,才需要同步处理;
JVM通过”-XX:+/-UseTLAB”指定是否使用TLAB。
-
-
对象内存初始化为零
对象内存初始化为零,但不包括对象头, 如果使用TLAB,可以提前至分配TLAB时进行,这保证了程序中对象(及实例变量)不显式赋值就直接使用,程序也能访问到零值。
接下来虚拟机对对象进行必要的设置,包括设置对象头信息,包括类元数据引用、对象的哈希码、对象的GC分代年龄等。
-
执行对象实例方法
此时从程序员的视角来看,对象的创建才刚刚开始。此时没有执行
方法,所有的字段都是初始零值。<init>
该方法把对象(实例变量)按照程序中定义的初始赋值进行初始化;<init>