当Java虚拟机遇到一条new指令时,会先检查这个指令的参数是否能在常量池中定位到一个类的符号应用,并检查这个符号引用代表的类是否已经被加载,解析和初始化过.如果没有则会进行响应的类加载过程.
虚拟机接下里为新生对象分配内存,所需内存的大小在类加载完成之后是完全确定的,为对象分配空间其实就是从内存中划分一块区域,有指针碰撞(Bump the Pointer)和空闲列表(Free List)两种方式.指针碰撞的实质是在一边为已用内存另一边为空闲内存的区域移动边界线,移动的距离就是对象所需的内存大小,它需要维护有序的两块内存.而当已用内存和空闲内存交错分布时,需要新的方式来维护内存分布信息,一般来说会采用空闲列表来记录内存中尚未使用的空闲内存.
使用Serial,ParNew等带压缩整理过程的收集器时会采用指针碰撞方法,既简单又高效;而当采用CMS这种基于清除(Sweep)算法的收集器时,理论上就只能采用空闲列表的方式.(在具体实现上,为了分配的更快,会使用Linear Allocation Buffer技术来分配缓冲区,使得局部有序,整体无序,通过空闲列表拿到一块分配缓冲之后,其中仍然采用指针碰撞的方式来分配.)
为对象分配内存是非常频繁的操作,仅仅修改指针指向的位置,在并发情况下是不安全的,解决这个问题有两种方案.
内存分配完之后,虚拟机必须将分配到的空间(不包括对象头)初始化为零,如果使用了TLAB的话,这一项工作也可提前至TLAB分配前进行.这步操作保证了对象的实例字段在Java代码中不赋初值就可以直接使用,程序访问到对应类型的默认零值.
接下来,JVM还要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据,对象的哈希码(会延迟到调用Object::hashCode()方法时计算),对象的GC分代年龄等信息.这些信息放在对象的对象头(Object Header)中,根据虚拟机的不同,例如是否启用偏向锁等,还会有不同的设置方式.
上述工作完成后,从虚拟机的角度看,一个新的对象已经产生了,但是从Java程序的角度看,对象创建才刚刚开始--构造函数,即Class文件中的<init>()方法还没有执行,字段还都是默认的零值,对象需要的其他资源和状态信息也没有按照预定的意图构造好.一般来说new指令之后会执行<init>()方法(由字节码流中new指令后是否跟随invokespecial指令决定,JVM在遇到new关键字后会产生这两个指令,但直接通过其他方式生成的则不一定如此),按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才被构造出来.
深入理解JVM第三版笔记(5)-HotSpot虚拟机对象揭秘
原文:https://www.cnblogs.com/SheezyGuo/p/12733471.html