简述类加载过程
1. 什么是类加载
类加载就是将class文件加载入内存。
2. 类加载共五大步骤
- 加载
- 验证
- 准备
- 解析
- 初始化
五大流程的启动顺序:
注意: 这仅仅是启动过程,也就是说,当加载过程开始,并不一定要等到加载结束才开始下一个过程,而是可以同步进行。
加载,验证,准备,初始化的先后启动顺序是固定的。
而解析则有两种启动顺序,分别是在准备过程之后,和初始化之后。这样做是为了支持动态绑定。
3. 什么时候触发类加载?
什么时候类加载?
见到class文件就加载吗?当然不是。
因此什么时候类加载,回答当然是需要的时候才类加载。
谁需要呢?当然是虚拟机需要的时候。
在考虑完加载动机和需求对象之后,那么我们可以很容易想到:
当因虚拟机需要而触发初始化过程的时候,类加载就已经开始了。
为什么初始化开始就代表类加载开始呢? 与前面说过的启动流程有关。既然启动流程固定顺序是:
加载→验证→准备→…初始化 那么既然触发初始化了,那么其他三个必要过程肯定也相应已经开始。
有且仅有的四种触发初始化方式:
- 使用new,或者调用类的静态方法等等。 从字节码来说,就是调用了new, getstatic, pustatic, invokestatic这四条字节码指令。
- 使用反射机制的时候,也就是与Class类有关的时候。
- 初始化一个类的时候,父类未初始化,就会触发父类的初始化。
- 包含main方法的类的初始化,会在启动虚拟机时触发。
4. 类加载的具体流程
五个加载过程并不是按照顺序完全独立执行的,而是经常同步执行。
加载过程
加载的目的:
- 通过类加载器获得二进制字节流。
- 二进制字节流加载入方法区。
- 在java堆中创建一个Class对象,作为方法区这个数据结构的访问入口。
验证过程
共有四种验证:
-
文件格式验证
开始时间:当在加载过程中获取二进制字节流时。
目的:验证字节码的结构是否符合虚拟机规范。
结果:验证成功后字节码被解析到方法区中供后面解析使用。 -
元数据验证
开始时间:文件格式验证之后。
目的:验证字节码的语义,也就是java语言的规范,如是否有父类,是否继承了final修饰的类等等。 -
字节码验证
开始时间:紧接着源数据验证之后。
目的:验证方法体中没有危害虚拟机的行为,但不保证验证过后一定不会出现这种行为。 -
符号引用验证
开始时间:解析过程开始之后。
目的:验证在将符号引用解析为直接引用时,常量池中的符号引用的信息是否正确。
准备过程
准备过程其实很简单,只需要注意与final关键字即可。
准备过程的目的: 仅仅对类变量进行赋值。
- 如果类变量没有final修饰符,赋值为初始值,如int为0,long为0L,char为’\u0000’ 等等。
- 如果类变量有final修饰符,如final static int a=123,那么就可以赋值为123.
解析过程
解析过程的目的:根据符号引用验证,将符号引用替换为直接引用。
符号引用的具体时机: 由虚拟机来判断,是在类加载器加载时进行解析,还是等到一个符号引用将要被使用前才进行解析。这也就造成了解析阶段无法确认具体的启动时间的原因。
初始化过程
初始化过程就是执行 <clinit> 方法。
<clinit> 是编译器通过收集类中的静态字段,静态代码块自动生成的。
这样的静态字段不会被收集:
- 如果没有赋值,而只是定义,那么不收集。
- 如果静态字段被final修饰,那么不手机。
不会收集的原因:编译器只会收集有赋值操作的静态字段,而如果被final修饰,那么同时被final,static修饰的字段会放入方法区的常量中,无需赋值操作。