Java对象的创建

https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485068&idx=1&sn=c37267fe59978dbfcd6a9a54eee1c502&chksm=cea24947f9d5c051008233a6a938e802b710ccf919f4215f84dcc0bf1fdad7d0101d37497d33&token=42880587&lang=zh_CN&scene=21#wechat_redirect

对象的创建

java创建对象过程

①类加载检查;②分配内存;③初始化零值;④设置对象头;⑤执行init方法

  1. 类加载检查:虚拟机遇到一条new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已经被加载过、解析和初始化过。如果没有,那就必须先执行响应的类加载过程。

  2. 分配内存:类加载检查通过后,接下来虚拟机将未新生对象分配内存。对象所需的内存大小在类加载完成后便可以确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。

    内存分配的两种方式:(指针碰撞和空闲列表)

    选择以上两种方式的哪一种,取决于Java堆内存是否规整。而Java堆内存是否规整,取决于GC收集器的算法是否是“标记-清除”,还是“标记-整理”(也称作“标记-压缩”),值得注意的是,复制算法内存也是规整的。

    • 指针碰撞法:假设Java堆中内存是完整的,已分配的内存和空闲内存分别在不同的一侧,通过一个指针作为分界点,需要分配内存时,仅仅需要把指针往空闲的一端移动与对象大小相等的距离。使用的GC收集器:Serial,ParNew,适用堆内存规整(即没有内存碎片)的情况下。

    • 空闲列表法:事实上,Java堆的内存并不是完整的,已分配的内存和空闲内存相互交错,JVM通过维护一个列表,记录可用的内存块信息,当分配操作发生时,从列表中找到一个足够大的内存块分配给对象实例,并更新列表的记录。使用的GC收集器:CMS,适用内存不规整的情况下。

    内存分配并发问题:在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须保证线程是安全的,通常来说,虚拟机采用两种方式来实现线程安全:

    • CAS+失败重试:CAS是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。

    • TLAB:为每一个线程预先在Eden区分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配。(Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域)

  3. 初始化零值:内存分配完成后,虚拟机需要将分配到内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型多对应的零值。

  4. 设置对象头:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希值、对象的GC分代年龄等信息。这些信息存方在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

  5. 执行init方法:在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程序的视角来看,对象创建才刚刚开始,方法还没有执行,所有的字段都还为零。所以一般来说,执行new指令之后还会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的访问定位

建立对象就是为了使用对象,我们的Java程序通过栈上的reference数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有①使用句柄②直接指针两种:

  • 句柄:如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与关系数据各自的具体地址信息。

  • 直接指针:如果使用直接指针访问,那么Java堆对象的布局就必须考虑如何放置数据类型数据的相关信息,而reference中存储的直接就是对象的地址。

    这两种访问方式各有优势。使用句柄来访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

你可能感兴趣的:(Java对象的创建)