我们编写的类.java文件,再被编译成.class文件储存成二进制字节码后,并不可以直接使用,必须经过类加载,一个类才可以被装载进运行时内存并被使用。因此理解类加载机制才能让我们更深刻地理解我们编写的java代码是如何一步一步的编译成.class文件,到如何在内存中正确的使用的过程。
package com.fanqiechaodan.classloader;
import com.fanqiechaodan.A;
/**
* @date 2021/6/23 22:07
* @declare
*/
public class ClassLoader {
private static final int i = 10;
public static void main(String[] args) {
int b = 15;
int sumByI = getSumByI(b);
A a = new A(sumByI);
}
/**
* 一个方法再内存中就有一块属于自己的栈帧区域
*
* @param b
* @return
*/
private static int getSumByI(int b) {
return i+b;
}
}
类的加载过程一共分为七大步:
- 加载:加载是类加载的第一个过程,在这个阶段,将完成三件事:
1.1. 通过一个类的全限定类名获取该类的二进制流
1.2.将二进制流中的静态储存结构转换未方法去运行时数据结构
1.3. 再内存中生成该类的.class文件,作为该类的数据访问入口 - 验证:验证的目的是为了确保class文件的字节流中的信息不会危害到虚拟机,再该阶段主要完成以下四种验证:
2.1.文件格式验证:如常量池中的常量是否有不被支持的类型
2.2.元数据验证:对字节码描述的信息进行语义分析,例如:这个类是否有父类,是否继承了不可以被继承的类等
2.3.字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语法是否正确,主要针对方法体的验证;例如:方法中的类型转换是否正确;跳转指令是否正确
2.4.符号引用验证:这个东西再后面解析的过程中发生,主要是为了确保解析动作可以正常的执行 - 准备:准备阶段是为了类的静态变量分配内存,并将其初始化为默认值,这些内存都再方法区中进行分配,准备阶段不分配类中的实体对象内存,实体对象内存会在对象实例化时伴随着对象一起分配再堆内存中
- 解析:该阶段主要是完成符号的引用到直接引用转换动作,解析的动作并不一定再初始化动作之前,也可能再初始化动作之后
- 初始化:初始化是类加载的最后一步,主要是对类的静态变量初始化为指定的值,执行静态代码块,前面的四步类加载的过程,除了再加载阶段可以通过自定义的类加载器参与之外,其他的完全是有虚拟机主导和控制
- 执行/使用:过了初始化阶段,才开始真正的执行类中定义的java代码
- 销毁:只有再以下四种情况下类才会销毁:
7.1. 执行了System.exit()
7.2.程序正常执行结束
7.3.程序再执行的过程中遇到了异常或者错误而终止
7.4.由于操作系统出现的错误而导致java虚拟机进程终止
类加载流程图:
注意:当前类再运行的过程中如果使用到其他的类时,才会逐步的去加载使用到的类,jar/war包里面的类不是一次性全部加载的,是使用到时才会进行加载;这种加载策略可以大大的节省程序启动的时间,再多态的情况下,如果使用到的类有很多子类/或者很多实现类,只有当代码执行到使用到引用类的时候才会知道具体有什么实现,加载具体的子类/实现类