Java作为一门面向对象语言,在运行过程中无时无刻都伴随着对象的创建。在语言层面,我们常常是用new来进行一个对象的创建,而其在Java虚拟机中的细节却要复杂许多,创建之后的内存布局访问方式也值得探讨,下面将以HotSpot虚拟机为例进行一一介绍。

JVM中对象的创建

1、类加载过程

       当虚拟机遇到一条字节码的new指令时,首先它会先检查是否能在常量池中定位到一个类的符号引用,并检查这个符号引用对应的类是否已经被加载,若未加载,会先进行类的加载过程

2、内存分配

       在类加载检查之后,JVM会在堆中为当前对象分配内存(大小在类加载之后就可确定)。根据堆中内存是否规整(受垃圾收集器影响)可分为两种方式:指针碰撞(Bump the Pointer)和空闲列表(Free List)。

       当堆中内存是规整的,即空闲内存在堆中一边,使用过的内存在另一边,我们使用指针碰撞,内存的中界有一个指针指向,我们只需要把指针往空闲部分移动一个对象内存大小的距离即可,这种方法较为简单高效。

       相反,当内使用过的内存和空闲内存交错在一起,我们使用空闲列表来解决,JVM维护一个列表记录哪些内存是可用的,当要分配对象时我们在列表中查找一块合适的内存进行分配,之后将列表进行更新即可。

3、初始化

       在这一步我们要将创建后的对象的内存空间初始化为零值,保证了对象的实例字段在不赋初始值就可直接访问。

4、对象头设置

       JVM需要对对象进行必要设置,例如声明该对象是哪个类的实例、如何找到类的元数据信息,对象的GC分代年龄等信息,这些信息存放在对象头中。

5、执行<init>( )方法

       一个对象的创建往往是用构造函数来进行预定意图的构造,这就对应着Class中<init>()方法,在上面步骤进行之后会执行<init>()方法来对对象进行初始化以满足程序员的需求,这样子一个真正可用的对象就创建完成。

 

对象的内存布局

       对象在堆内存中的布局可划分为三个部分:对象头、实例数据和对齐填充

1、对象头

       对象头主要存储两类信息。一是对象自身的运行时数据,如哈希码,该对象的GC分代年龄、锁状态标识,线程持有的锁等。二是类型指针,即对象指向他的类型元数据的指针,JVM通过这个指针来确定其所属的类。若对象是一个数组,对象头还应该有一块用来记录数据长度。

2、实例数据

       实例数据存储的就是真正的对象有效信息,即程序代码中所定义的各个类型的字段信息,无论是父类还是当前类的信息都应该存储。

3、对齐填充

       这部分并没有实际意义,因为hotspot JVM要求对象起始地址必须是8字节,这部分起到占位符的作用。

 

对象的访问

       创建一个对象之后我们会在之后进行引用,Java程序会通过虚拟机栈中的reference进行对象引用,而reference主要通过两种方式进行对象访问。

       第一种情况是句柄访问,reference存储的是句柄地址,句柄中包含了对象实例数据和类型数据的各自地址。

       第二种是直接访问,reference中存储的是对象地址。

具体如图所示。

JVM中对象的创建细节、内存布局及访问

JVM中对象的创建细节、内存布局及访问

 

两种方式各有优点,当我们使用句柄时,即使对象被移动,我们的reference不用被修改,只要修改句柄即可。而直接访问则速度更快,节省了一次地址读取的时间。


以上即是对象在JVM中创建、内存布局和访问的细节,是我在读《深入理解Java虚拟机》之后的理解总结,之后将会随着学习进度进行更新。

 

 

 

 

 

 

 

 

 

相关文章: