读《深入理解Java虚拟机》- Java对象创建过程

本文较为浅显的说明一下new一个Java对象的过程是怎么样的,对象头到底存了啥,已经对象的访问定位方式。里面会涉及到其他知识,等总结后再把链接贴上来。

Object obj = new Object();

1、虚拟机检测到new关键字时,先检查这个new的参数(也就是类名)在常量池中有没有一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析、初始化过了,如果没有,需要先执行类的加载(类的加载过程下一篇总结)

2、如果已经加载过类了,那么就开始为对象分配内存空间。具体分配多大的空间在类加载的时候就已经确定了,现在需要在堆区找到一块内存空间。有两种方法:

2.1、指针碰撞:假设堆里面的内存是规整的,也就是已分配的空间在一边,没分配过的在一边(老年代的标记-整理?)。这样只需要一个指针在内存区域内移动需要分配的内存大小的距离即可。

2.2、空闲列表:假设堆里面的内存不是规整的(大部分是这样(新生代)),那就需要维护一个内存分配的列表,记录着哪些内存块是可用的,找到一个可用的块分配给新new的对象即可。

3、分配内存是一个频繁的过程,且不是一个原子操作(在空闲列表找可用内存块---分配内存---维护空闲列表(这三个只是粗略的步骤))。在多线程环境下,很可能发生并发问题。所以虚拟机提供了两种方案来解决并发问题:

3.1、CAS+重试:实际上虚拟机是采用了CAS算法,让分配内存的动作首先变成原子操作,然后加上分配失败重试即可(有点笼统,下次附详细步骤的链接)

3.2、TLAB:利用线程私有的TLAB区域来分配内存空间是保证并发操作的不错手段,但是在当前TLAB内存不够,需要新增另一个TLAB空间时需要加锁(这个新增的过程需要保证原子性)。

4、初始化已分配的内存空间:把分配到的内存空间初始化为零值(不包括对象头),如果是通过TLAB方式分配的,那这个初始化零值的过程可以在分配阶段完成。这个操作保证了我们在使用类的成员变量时即时不初始化或不赋值也能使用,其值为零。

class A{int a;}

5、设置对象头信息:对象是哪个类的实例,如何找到类的元数据信息,对象的哈希码,对象的GC分代年龄

6、init方法:执行代码里面的初始化信息。class B{int a = 1;}

 

对象在内存中存储布局分为对象头,实例数据,对齐填充

什么是对象头?

对象头包括对象自身的运行时数据和类型指针。

运行时数据:哈希码,GC分代年龄,索状态标志,线程持有的索,偏向线程ID,偏向时间戳

类型指针:指向对象的类的元数据指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

另外,如果对象是一个数组,那么对象头中还存放了数组的长度。(对象的元数据确定对象的大小,加上数组长度确定数组对象的大小)

 

对象的访问定位:

句柄和直接指针,直接看图就一目了然了。

读《深入理解Java虚拟机》- Java对象创建过程_第1张图片

 

读《深入理解Java虚拟机》- Java对象创建过程_第2张图片

通过句柄存放,栈中的本地变量表的reference引用的值不需要变化,当发生GC,导致对象移动时,只要改句柄中的引用即可。不好的就是需要两次寻址才能找到。

通过直接引用的方式是速度快。

你可能感兴趣的:(读《深入理解Java虚拟机》- Java对象创建过程)