一:类加载之前的检查
当我们new一个对象的时候,jvm先检查指令的参数能否在常量池中定位到这个类的引用符号引用。如果有,则证明这个对象被加载过,直接跳到init。然后检查这个符号引用代表的类是否已经被加载、解析和初始化过。没有才进行相应的类加载过程。
二:分配内存
先为这个类分配相应的内存,首先是分配到哪的问题,如果是并发分配内存时,存在一个安全的问题。当两个线程同时分配了同一块内存就会出现错误。这个时候出现了两种解决方式,方式一:TLAB(在Eden区为每一个对象都建立一块小内存,大小为年轻代的1%,每个对象先在自己的区域分配内存。自己的区域满了在使用公共内存)。方式二:CAS分配(采用乐观锁,发现地址被占用就重新分配)。
其次还有一个怎么分配的问题,也是两种方式:指针碰撞和空闲列表。至于什么时候采用哪种方式呢,一句话——看你的垃圾收集器或者垃圾收集算法是什么。当你的jvm使用的是标记清除算法也就是对应使用的是serial +serial old或palleraler+parreraler old。
那垃圾收集完之后就会像上图一样,剩余的空间不规整。就需要维护一个空闲列表,记录下那几块内存是可用的。分配内存的时候就去空闲列表找对应大小的内存就行了。
当你的jvm使用的是标记整理算法也就是对应使用的是CMS+parNew(CMS用的是标记整理,parNew还是使用的是标记清除。只是因为他们总是成对使用)
上面是清理前后的内存对比图。由于垃圾回收完之后内存是整洁有序的。所以再次为对象分配内存时可以接着依序分配就好了,这个时候使用的就是指针碰撞。
三:初始化为零
这一步很简单,就是将刚才分配的内存初始化为零(不包括对象头),这里说的全部初始化为零,只是说全部找一个值赋值给变量,而不是全部赋值等于零,比如说boolean类型就赋值等于false.
四:设置对象头
对象头存放的是对象的哈希码、GC分代年龄、是否使用偏向锁、这个对象是哪个类的实例等信息。
五:执行init方法
在完成上面的操作之后,对于虚拟机来说,一个新的对象已经生成,但是在java程序层面来说,对象创建才刚刚开始,init方法还没有执行,所有字段的值都还是”零“。所以一般来说,new指令之后会执行init方法,把对象按照程序员的意愿进行初始化。