一个java文件从编码完成到最终执行,一般主要包括两个过程:编译和运行。编译,即是写好的Java文件通过javac命令编译成字节码,也就是常见的.class文件;运行,则是把编译.class文件交给Java虚拟机(JVM)运行。
类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。
例子:JVM 在执行某段代码时,遇到了 class A,然而此时内存中并没有 class A 的相关信息,于是 JVM 就会到相应的 class 文件中去寻找 class A 的类信息,并加载进内存中,这就是所说的类加载过程。
类加载过程分为 加载、验证、准备、解析、初始化、使用、卸载 7个阶段
1、加载
加载阶段主要是:
(1)、通过类的权限定名获取类的二进制文件流
(2)、将字节流所代表的静态存储结构转变为方法区的运行时数据结构
(3)、在堆中生成此类的java.lang.class对象,作为方法区这些数据的访问入口
加载阶段是开发可控性最强的阶段,比如说开发可以使用自定义类加载器去加载某个类,类的来源也可以是jar包、class文件、甚至是网络流。
重点:
(1)、字节码来源:一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
(2)、类加载器。一般包括启动类加载器(BootstrapClassLoader)、扩展类加载器(ExtensionClassLoader)、应用类加载器(ApplicationClassLoader)、用户的自定义类加载器(CustomerClassLoader)。
注:为什么会有自定义类加载器?
(1)、由于 java 代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。
(2)、也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。
2、验证
验证是连接过程的第一步,它是为了确保class文件的字节流符合虚拟机的规范。
加载阶段加载class文件,并未规定class文件的来源,如果愿意,甚至可以手动编写class文件,但这样的class文件有可能不符合虚拟机规范,所以需要验证。它主要进行以下方面验证:
(1)、文件格式验证
(2)、元数据验证
(3)、字节码验证
(4)、符号引用验证
3、准备
准备阶段是为类变量正式分配内存并赋默认值的阶段,这些内存都在方法区内分配。
强调:只为类变量分配内存,即是为static变量分配内存,一般成员变量是在类被实例化时分配内存;只会为static变量赋默认值。假设有static变量:
public static int val = 123;
那么准备阶段,val的值会变成0,而不是123。而把val赋值为123的putstatic指令是在程序被编译后,存放在 clinit 方法中,所以val被赋值为123 发生在初始化阶段,即第5个阶段。
如果是final修饰的static变量呢?如果字段的字段属性表中存在ConstantValue属性,那么准备阶段,变量就会被初始化为ConstantValue存储的值。
假设上述变量为:
public static final int val = 123;
编译时javac会为val生成ConstantValue属性,准备阶段value的值就会成为123。
4、解析
解析阶段是将常量池中的符号引用替换为直接引用的过程。
(1)、符号引用是指字面量,能无歧义地定位到目标就好,它与虚拟机的内存布局无关。
(2)、直接引用是指可以直接访问到对象的指针或句柄,与内存布局相关的。
解析主要针对类或接口、字段、类方法、接口方法4类符号引用。
5、初始化
初始化是类加载过程的最后一步,除了加载过程,开发可以使用自定义类加载器参与外,其它过程全都是虚拟机自己控制着。初始化阶段,才真正开始执行Java程序代码。
准备阶段,为static变量分配内存并赋默认值,而初始化阶段则会执行clinit方法,为static变量分配代码中所指定的值。
(1)、
(2)、
(3)、因为父类的
(4)、