类加载机制

70 阅读4分钟

静态加载 与 动态加载

  1. 静态加载:编译时加载相关的类,如果没有则报错,依赖性强
  2. 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性(花费了额外的解析时间,也会造成效率的下降)

代码

public class Reflection {
    public static void main(String[] args) 
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Scanner scanner = new Scanner(System.in);
​
        int key = scanner.nextInt();
        if (key == 1) {
            //  静态加载:无论key是否为1,JVM都会加载Cat类
            Cat cat = new Cat();
        } else {
            //  动态加载:只有key不是1的时候,JVM才会加载Dog类
            Class clazz = Class.forName("com.suen.reflection.Car");
            Dog dog = (Dog) clazz.newInstance();
        }
    }
}

类加载时机

  1. 创建对象【静态加载】
  2. 子类被加载,父类也被加载【静态加载】
  3. 调用类中的静态成员【静态加载】
  4. 反射【动态加载】

过程图

过程图

类加载各阶段解析

1. 加载

  • 目的:JVM调用类加载器字节码从不同的数据源(可能是class文件jar包网络)转化为二进制字节流加载到内存中( 方法区 二进制文件 + 堆中的Class对象

  • 类加载器使用了按照双亲委派机制进行加载

    • 双亲委派模型的工作过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,因此所有的加载请求最终都会委派到启动类加载器中。只有父类加载器反馈自己无法加载这个类时,子类加载器才会尝试自己去加载。

    • 优点:

      • 可避免类的重复加载。
      • 避免了java的核心API被篡改。

2. 链接

2.1 验证(vertification)

  • 目的:确保Class文件的字节流中包含的信息 符合当前虚拟机 的要求,并且 不会危害虚拟机 自身的安全。
  • 包括:文件格式验证(是否以 魔数ox cafe babe 开头)、元数据验证、字节码验证 和 符号引用验证
  • 可以考虑使用 -Xverify:none 参数来 关闭大部分的类验证措施,缩短虚拟机类加载时间

2.2 准备(Preparation)

  • JVM会在该阶段对 静态变量分配内存默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。
  • 这些变量使用的内存都将在 方法区 中进行分配
class A {
    //1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
    public int n1 = 10;
    //2. n2 是静态变量,分配内存n2 是默认初始化0 ,而不是20
    public static int n2 = 20;
    //3. n3 是被final修饰的static字段,不会设置,因为final在编译的时候就分配了   
    public static final int n3 = 30;
}

2.3 解析(Resolution)

  • 目的:是将常量池内的符号引用转换为直接引用的过程(将常量池内的符号引用解析成为实际引用)。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)
  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义定位到目标即可。
  • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

3. 初始化

  • 真正开始执行类中定义的Java程序代码,此阶段是执行 <clinit>() 方法的过程
  • <clinit>() 方法是 编译器 按 语句在源文件中出现的顺序 依次自动收集类中所有 静态变量 的赋值动作和 静态代码块 中的语句,并进行合并
  • 虚拟机 会保证一个类的 <clinit>() 方法在多线程环境中被正确的加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>()方法完毕
  • 调用类的静态属性会造成类的初始化