JVM中类的加载过程和对象的创建过程之间的差别
文章目录
我对JVM类加载过程中的Initailization过程和对象创建过程的错误理解
最初学习这两个过程的时候,自己就没有完全分清楚,老是觉得这两者说的应该是一个过程。而这次带着疑问
再次学习JVM资料,终于发现问题所在。
对于JVM层面而言,类的初始化过程本质调用了JVM实现的<clinit>()
方法,而对象的初始化过程调用的是
<init>()
方法。所以它们完全是两个过程,执行有先后关系(类初始化之后才执行对象初始化)。
<clinit>()
:用来初始化类属性(给static属性赋予程序所编写的默认值),以及加载一些只保存一份的资源(因为会调用父类和当前类中的static代码块,例如:static代码块中加载图片资源)
<init>()
:用来初始化对象的属性(赋程序所编写的默认值)和对象的构造方法(当前对象以及父类的构造方法)
JVM中创建对象的过程
在JVM中要完整地创建对象需要使用两个JVM指令,分别是new
和invokespecial
指令。基于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
文章作者 xiaoqi
上次更新 2020-05-24