Java-类加载过程
- Java虚拟机类加载全过程包括 加载,验证,准备,解析和初始化
1.加载
- 加载是整个类加载过程中的一个阶段,完成的事情:
- 通过一个类的全限定名来获取此类的二进制字节流
- 注意这里不一定非要从一个Class文件中获取,也可以从ZIP包中,运行时计算中得到
- 将这个字节流代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 通过一个类的全限定名来获取此类的二进制字节流
- 加载阶段既可以使用Java虚拟机内置的引导类加载器来完成,也可以由用户自定义的类加载器完成
- 数组本身不通过类加载器创建,而是由Java虚拟机直接在内存中动态构造出来的
2.验证
-
为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,不危害虚拟机安全
-
文件格式验证: 验证字节流是否符合Class文件格式规范
- 主次版本号是否在JVM接收范围
- 常量池的常量中是否有不被支持的常量类型
-
元数据验证: 对字节码描述的信息进行语义分析
- 这个类是否有父类
- 父类是否继承了不该继承的类(比如被final修饰的类)
-
字节码验证: 验证阶段最复杂的阶段,通过数据流和控制流分析,确定程序语义是合法,符合逻辑的
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作
- 保证任何跳转指令都不会跳转到方法体以外的字节码指令
-
符号引用验证: 发生在虚拟机将符号引用转为直接引用的时候
- 符号引用中通过字符串描述的全限定名是否能找到对应的类
- 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
3.准备
-
是正式为类中定义的变量(即静态变量)分配内存并设置初始值,应当在方法区分配;JDK7之前HotSpot使用永久代实现方法区,内存分配在方法区,JDK8及之后,类变量会随着Class对象一起放在Java堆
public static int value = 8000; 实际上value在准备阶段的初始值为0而不是8000,将value赋值为8000是在程序被编译后,存放在构造器<clinit>()方法中 public static final int value = 10; 在编译阶段会为value生成ConstantValue属性,在准备阶段会赋值为10
4.解析
-
解析阶段是Java虚拟机将常量池内的符号引用转化为直接引用的过程
-
符号引用就是class文件中的CONSTANT_Class_info、 CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量
-
符号引用: 一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,引用的目标并不一定是已经加载到虚拟机内存当中的内容。
-
直接引用: 是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄;如果有直接引用,那引用的目标必定存在;直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同
5.初始化
-
初始化阶段是类加载的最后一个阶段,除了在加载阶段可以自定义类加载器以外,其它操作都是JVM主导。到了初始化阶段,才开始真正执行类中定义的java代码
-
初始化阶段就是执行类构造器()方法的过程
-
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的
-
如果一个类中没有对静态变量赋值也没有静态语句块,编译时可不用生成()方法
-
以下情况不会进行初始化
- 通过子类用用父类的静态字段,只会出发父类的初始化,不会出发子类的初始化
- 定义对象数组不会出发该类的初始化
- 通过类名获取Class对象,不会触发初始化
- 通过ClassLoader默认的LoadClass方法也不会出发初始化
转载自原文链接, 如需删除请联系管理员。
原文链接:Java-类加载过程,转载请注明来源!