我对JVM类加载过程中的Initailization过程和对象创建过程的错误理解

​ 最初学习这两个过程的时候,自己就没有完全分清楚,老是觉得这两者说的应该是一个过程。而这次带着疑问 再次学习JVM资料,终于发现问题所在。 ​ 对于JVM层面而言,类的初始化过程本质调用了JVM实现的<clinit>()方法,而对象的初始化过程调用的是 <init>()方法。所以它们完全是两个过程,执行有先后关系(类初始化之后才执行对象初始化)。

<clinit>():用来初始化类属性(给static属性赋予程序所编写的默认值),以及加载一些只保存一份的资源(因为会调用父类和当前类中的static代码块,例如:static代码块中加载图片资源)

<init>():用来初始化对象的属性(赋程序所编写的默认值)和对象的构造方法(当前对象以及父类的构造方法)

JVM中创建对象的过程

​ 在JVM中要完整地创建对象需要使用两个JVM指令,分别是newinvokespecial指令。基于JVM指令帮助文档以及HotSpot中new指令源码可以知道这两个指令的大体执行过程。(此处创建的对象是Java的普通对象,除去数组、Class等对象)

  • new指令

首先传递给new指令的参数是两个字节,通过两个字节可以计算出当前class字节流常量池的索引。 通过常量池找到一个class的符号引用(例如:类名),检查该符号引用的类是否经历过类的加载过程(Loading、Linking、Initailization),如果没有加载会去加载该类,否则通过该类的常量池等信息可以计算出创建该对象的内存大小。在JVM堆中 进行内存的分配。被分配的内存有该对象的各个实例属性的系统默认值,并且基于当前对象的状态,给对象头赋予对应属性。 此时会将是内存空间的首地址(objectref)push到当前JVM栈帧的操作数栈中。

  • invokespecial指令

该指令会调用实例的父类和自身的构造方法(即JVM中的<init>()方法),用来给对象赋予程序中所定义的初始值(例如:int a = 1;a此时的值从0变成1)。还会将操作数栈中的objectref弹出。

​ 总结下来:

1 检查要创建对象的类是否已经完成类加载过程

2 计算出对象所需内存空间大小,在JVM堆中为其分配内存,为该内存空间赋系统默认值,为对象头赋予基于状态的对应值

3 调用该对象的构造方法,为该对象赋予程序定义的初始值。

4 此时就创建出了一个完整的对象

类加载过程和对象创建过程的关系

​ 首先前面所知,对象创建之前首先要找到一个已经完成类加载过程的class(内存中的class字节流)。因此此处简单说明一下类加载的 过程。类加载过程分为三个大阶段,每个阶段开始执行的时间是按先后顺序的,但不是一个阶段结束后才会进行下一个阶段,而是在某个 阶段的执行过程中,另一个阶段就可能开始执行。

​ 类加载过程的三个阶段分别是Loading、Linking、Initailization。

  • Loading过程,通过类的完全限定名找到其对应的class字节流,将其加载到方法区中,并将其构建成内存中的数据结构。
  • Linking过程,该过程还可以细分为以下子过程
    • validate: 用来校验class字节流是否合法,校验内存中class结构中的数据是否符合法。
    • prepration:准备阶段,用来初始化类属性(即static修饰的变量),给这些变量赋系统默认值(如果有final关键字,则直接赋程序定义的默认值)
    • resolve:将class常量池中的符号引用转换为直接引用。该过程可以在使用到该引用时在执行。
  • Initailization过程,给类属性赋程序定义的默认值,并执行static代码块中的内容。

带着问题学习的效果更好

​ 最初的几次学习可能只是一种看热闹的状态,没有真正理解所学的东西到底在说啥。而当自己面对一个问题时, 自己要去回答它,就会发现自己所理解部分的缺失,然后基于所缺失的问题,重新学习。最终让自己真正的理解 这个知识点或者所学的东西。

参考资料

《深入理解Java虚拟机》2.3.1、7.3

《The Java® Virtual Machine SpecificationJava SE 8 Edition》5