Java的对象几乎都产生于堆中,但是对于对象是怎么产生的,具体在new过程中执行了什么,不能不清楚,至少也应该知道一些知识点。
JVM在new过程中执行了5个步骤去创建一个对象:
1.相应类的加载检查。
2.为对象分配内存。
3.对象内存初始化为零。
4.对对象进行必要设置。
5.执行对象实例<init>方法进行初始化。
1.相应类的加载检查。
类是否已经加载是需要确定的,我们知道类的加载只执行一次,类加载过程中会对类的静态属性进行初始化。那么JVM在遇到new指令时,先检查指令参数是否能子啊常量池中定位到一个类的符号引用。
1)如果定位到,表名这个类已经被加载,解析,初始化过,可以直接使用了。
2)如果定位不到,就需要先去执行类的加载。
2.为对象分配内存。
对象的内存在类加载完成之后变完全确定了,因此为对象分配内存就是把一块确定大小的内存从Java堆里划分出来。就有必要讨论一下分配方式了。
2.1)分配方式:
指针碰撞:它的前提是Java堆是绝对完整的,一边是用过的内存,一边是空闲的内存,中间存在一个指针作为边界指示器。关于这个指针碰撞,其实比较感兴趣,但 是网上搜了一圈,毛都没有,都是一笔带过~~~~希望知情者能提供相应的介绍文档。
空闲列表:那么如果Java堆不是完整的,用过的和空闲的相互交错,需要维护一个列表,记录哪些内存是可用的。在分配时查表找到一个足够大的内存,并更新列 表。
这边可以看见,Java堆是否完整其实取决于采用了哪种垃圾回收算法,带有标记-整理的算法,可以采用指针碰撞的方式。比如Serial,ParNew,而CMS需要采用空闲列表
2.2)线程安全问题:
看到这边的时候我想到的是单例里会出现对象分配到一半的情况,后续去研读设计模式时可以看下。
并发出现时,上述两种分配方式都不是线程安全的。
同步处理:即CAS理念,对于CAS是啥不做解释,
本地线程分配缓冲区:Thread Local Allcation Buffer(TLAB)。这就将内存分配的动作按照线程划分在不同的空间中进行,当TLAB用完之后分配新的TLAB时做同步 处理。
3.对象内存初始化为零。
这一步和类加载过程中链接里的准备阶段一致,将分配到内存中都初始化为零值(对象头不在其内),如果使用了TLAB方式,这一步可以提前至TLAB分配时进行。这一步确保了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的对应的零值,注意这边还没有进行初始化步骤,可以理解为成员变量的初始化操作。
4.对对象进行必要设置。
必要的设置过程,类似,对象是哪个类的实例等等。
5.执行对象实例<init>方法进行初始化。
此步骤才会执行对象的初始化操作,就是实例构造器,这个构造器其实不止构造函数这么简单,我所知道的包括实例代码块,实例变量的初始化,最后才是构造函数。