jvm 类加载机制分为五个部分 加载、验证、准备、解析、初始化。
一、加载
1、通过类的全限定名来获取此类的二进制字节流
2、将此类字节流代表的静态存储结构转行为方法去区运行时的数据结构
3、在堆中生成一个代办此类的java.lang.Class对象,作为方法区这个类的各种数据的入口
二、验证
这一阶段的主要目的是为了确保Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
三、准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为
public static int a = 123;
实际上变量a 在准备阶段过后的初始值为0 而不是123,将a 赋值为123 的put static 指令是
程序被编译后,存放于类构造器方法之中。
但是注意如果声明为:
public static final int a = 123;
在编译阶段会为a生成ConstantValue 属性,在准备阶段虚拟机会根据ConstantValue 属性将a
赋值为123。
四、解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class 文件中 CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info等类型的常量。
符号引用
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。符号引用于虚拟机的实现布局无关。引用目标不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。
比如org.simple.People类引用org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
直接引用
(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
(3)一个能间接定位到目标的句柄
直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。
五、初始化
初始化阶段是类加载的最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载之外,其他操作都由jvm主导。到了初始化阶段,才真正开始执行类中定义的java代码。