Java对象与JVM(一) Java对象在Java虚拟机中的创建过程

       在《Java内存区域 JVM运行时数据区》文章了解到Java中几乎所有的实例对象存储在Java堆内存中。

       下面我们详细了解Java程序中new一个普通对象时,HotSpot虚拟机是怎么样创建这个对象的,包括5个步骤:相应类加载检查过程、在Java堆中为对象分配内存、分配后内存初始化为零、对对象进行必要的设置、以及执行对象实例方法<init>,最后我们再从JVM指令角度来解释下Java对象创建。

1、相应类加载检查过程

       通过《JVM字节码指令及反编译分析》可以知道:Java程序中的“new”操作会转换为Class文件中方法的“new”字节码指令。

       JVM(本文特指HotSpot)遇到new指令时,先检查指令参数是否能在常量池中定位到一个类的符号引用:

       (A)、如果能定位到,检查这个符号引用代表的类是否已被加载、解析和初始化过;

       (B)、如果不能定位到,或没有检查到,就先执行相应的类加载过程;

2、为对象分配内存

      对象所需内存的大小在类加载完成后便完全确定(JVM可以通过普通Java对象的类元数据信息确定对象大小);

      为对象分配内存相当于把一块确定大小的内存从Java堆里划分出来;

(A)、分配方式:

(I)、指针碰撞

      如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;

      分配内存只需向空闲那边移动指针,这种分配方式称为"指针碰撞"(Bump the Pointer);

(II)、空闲列表

      如果Java堆不是规整的:用过的和空闲的内存相互交错;

      需要维护一个列表,记录哪些内存可用;

      分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为"空闲列表"(Free List);

      Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的;

      所以,使用Serial、ParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存;而使用CMS这种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式;

      后面再介绍垃圾收集算法和垃圾收集器,了解垃圾收集时应注意这里的内容;

(B)、线程安全问题

      并发时,上面两种方式分配内存的操作都不是线程安全的,有两种解决方案:

(I)、同步处理

      对分配内存的动作进行同步处理:

      JVM采用CAS(Compare and Swap)机制加上失败重试的方式,保证更新操作的原子性;

      CAS:有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做;

(II)、本地线程分配缓冲区

      把分配内存的动作按照线程划分在不同的空间中进行:

      在每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer,TLAB);

      哪个线程需要分配内存就从哪个线程的TLAB上分配;

      只有TLAB用完需要分配新的TLAB时,才需要同步处理;

JVM通过"-XX:+/-UseTLAB"指定是否使用TLAB;

3、对象内存初始化为零

      对象内存初始化为零,但不包括对象头;

      如果使用TLAB,提前至分配TLAB时;

      这保证了程序中对象(及实例变量)不显式初始赋零值,程序也能访问到零值;

4、对象内存初始化为零

      主要设置对象头信息,包括类元数据引用、对象的哈希码、对象的GC分代年龄等(详见下节);

5、执行对象实例方法<init>

      该方法把对象(实例变量)按照程序中定义的初始赋值进行初始化;

        

      通常,经过上面5步对象才完全new出来。

      另外,还可以参考HotSpot虚拟机源码中的"bytecodeInterpreter.cpp"文件,这个文件有表示解释器处理"new"指令基本类似上面的5个过程。

6、Java对象创建的JVM指令

      通过前面一些文章,我们还可以从JVM指令的角度来看对象的创建过程:

(A)、new指令

      "new"指令有一个类符号引用的常量,JVM解析该常量也就对应步骤1"相应类加载检查过程";

      "new"指令执行完毕后,一个代表(指向)该对象实例内存数据的reference类型变量数据将压入到操作数栈中

(B)、dup指令

      接着会执行"dup"指令复制该reference数据,这时操作数栈栈顶就有两个指向该对象实例内存的reference数据;(如果<init>方法有参数,还需要把参数加载到操作栈

(C)、invokespecial指令

      再执行"invokespecial"指令调用对象实例方法<init>,这时操作数栈最上面的一个reference数据会出栈(如果有参数,包括方法参数);

      然后在Java虚拟机栈中创建<init>方法的栈帧,把出栈的reference数据(和参数)放入该栈帧的局部变量表中,该reference数据在方法中也就是"this",表示对该对象实例进行的操作

      当然这些参数的数值、数据类型和顺序都必须遵循实例方法的描述符中的描述;

      另外,操作数栈中还有一个对象reference数据一般被“astore”到局部变量表或保存到字段变量,给后面访问对象使用。

      到这里,我们大体了解Java对象在HotSpot虚拟机中的创建过程, 后面我们将分别去了解:对象的内存布局、对象的访问定位、方法的调用与执行、JIT编译、以及JVM垃圾收集相关内容……

猜你喜欢

转载自blog.csdn.net/qq_38384440/article/details/81662101