Java 类的加载过程

类的生命周期:加载,验证,准备,解析,初始化,使用和卸载

类的加载分为5个阶段:加载,验证,准备,解析,初始化,其中验证,准备,解析总称为连接

虚拟机规范没有对类加载的时机强制约束,可以由虚拟机具体实现自由把握,但是,虚拟机严格规范了初始化的情况,加载,验证,准备须在此之前进行

有且仅有四种情况必须立即对类进行初始化:

1)遇到new,getstatic,putstatic或invokestatic这四条字节码指令时,如果类没有初始化,则需立即初始化

2)使用反射来调用类时,如果类没有初始化,则需立即初始化

3)初始化一个类时如果发现其父类还未初始化则需先初始化其父类

4)虚拟机启动时会执行主类,如果主类还未初始化则初始化其主类

被动初始化,不会触发类的加载:

1.通过子类调用从父类继承的静态变量,只会加载父类,不会加载子类

2.SomeClass [] array = new SomeClass[10]; 不会加载该类

3.调用静态常量不会触发类的加载

1.加载

加载过程主要做三件事

1)通过类的全限定名获取该类的二进制字节流

可以从不同的地方获取,例如class文件,jar压缩包,网络,运行时生成等等,具体可以通过自定义类加载器完成

2)将该字节流储存到方法区

按照虚拟机所需的格式储存,具体由虚拟机实现

3)在堆中生成该类的Class对象,作为方法区这些数据的访问入口

加载和连接阶段时交叉进行的,如字节码的加载和字节码文件格式的验证,但是连个阶段的开始时间是有先后顺序的

2.验证

目的:确保字节流中的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全,主要有四个阶段:

1)文件格式验证,可能包括以下几个验证点:

1))是否以魔数0xCAFEBABE开头

2))主,次版本号是否在当前虚拟机处理范围内

3))常量池中是否有不被支持的常量类型(检查常量tag标志)

4))指向常量池的索引值中是否有指向不存在的常量或不符合类型的常量

5))CONSTANT_Utf8_info型的常量中是否有不符合utf8编码得数据

6))Class文件中各个部分及文件本身是否有被删除或附加的其他信息

经过该阶段,字节流才会进入内存的方法区进行储存,所以后面的三个验证全是基于方法区的储存结构进行的

2)元数据验证

目的:对字节码信息进行语义分析,以保证其描述的信息符合Java语言规范要求,可能包括以下几个验证点:

1))检查该类是否有父类(除了Object外,其他类均应该有父类)

2))这个类的父类是否继承了不被允许继承的类(被final修饰的类)

3))如果这个类不是抽象类,其是否实现了其父类或接口要求实现的所有方法

3)字节码验证

验证过程中最复杂的阶段,主要工作是进行数据流和控制流分析

保证方法体中的类型转化是有效的

4)符号引用验证

发生在虚拟机将符号引用转换为直接引用的时候,这个转换将在解析阶段发生,通常由以下类容:

1))符号引用中通过字符串描述的全限定名能否找到对应的类

2))指定类中是否存在符合方法的字段描述及简单名称所描述的方法和字段

3))符号引用中的类,字段和方法是否可被当前类访问

验证阶段很重要,但不是必要,如果代码是自己编写的,就不必反复验证

3.准备

在方法区为类变量(不包括实例变量)分配空间并设置初始值

通常情况下,初始值为该类型默认值,但是如果该变量还被修饰为final的话就直接初始为其设定值

public static int value = 345;         初始为0

public static final int value = 345; 初始为345

4.解析

将常量池中的符号引用转换为直接引用

符号引用(Symbolic References):用一组符号来描述所引用的目标,引用的目标不一定已经加载到虚拟机中

直接引用(Direct References):可以是指向目标的指针,相对偏移量或者句柄,如果由直接引用那么目标一定存在与虚拟机内存中

解析目标主要为类或接口(CONSTANT_Class_info),字段(CONSTANT_Fieldref_info),类方法(CONSTANT_Methodref_info),接口方法(CONSTANT_InterfaceMethodref_info)四类符号引用,以下为四类符号的解析过程:

类或接口的解析:

如果要加载的类不是数组类型,则将该符号应用对应的全限定类名传递给当前类的类加载器加载该类,其中涉及到该类的父类或接口的加载

如果时数组类型,则加载该数组所对应类型的类,接着由虚拟机生成该数组的维度和元素的数组对象

最后检查access_flags看当前类对加载类是否有访问权限

其他三类的加载大体相同:

首先加载对应的类或者接口,当前类或者接口是否有目标,若没有则在父类中寻找目标(字段是父接口),若没有则在父接口中寻找目标(字段是父类),若没有解析失败,若有则进行权限验证

5.初始化

执行类构造器<cinit>()方法,该方法是由类变量和static块中的语句合并产生的,语句顺序与源文件中出现顺序一致;在静态块中可以为定义在其后的变量赋值,但是不可以访问

虚拟机会保证初始化子类前初始化其父类,所以Object类最先初始化;需要注意的是,初始化类或接口时不会初始化其实现的接口或父接口,只有在用到父接口中的变量时才会初始化

最后<cinit>()方法不是必须的,如果没有static变量和static块的话就可以不生成该方法

原文地址:https://www.cnblogs.com/xiao-ji-xiang/p/9836963.html