0%

JVM类的生命周期和类加载器

  • 类的生命周期
    • 加载阶段
    • 连接阶段
    • 初始化阶段
  • 类加载器

1.3 类的生命周期

  • 类的生命周期描述了一个类加载、使用、卸载的整个过程。

1.3.1 加载阶段

  1. 加载 (Loading) 阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。
  2. 类加载器在加载完类之后,Java 虚拟机会将字节码中的信息保存到方法区中,会生成一个 InstanceKlass 对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。
  3. 同时,Java 虚拟机还会在堆中生成一份与方法区中数据类似的 java.lang.Class 对象。作用是在 Java 代码中去获取类的信息以及存储静态字段的数据。
  • 总的来说加载阶段就是类加载器获取字节码文件的信息并加载到内存中,然后 Java 虚拟机根据字节码文件的信息在方法区和堆中生成类的相关对象

1.3.2 连接阶段

  1. 验证:主要目的是检测 Java 字节码文件是否遵守了《Java虚拟机规范》中的约束。
  2. 准备:为静态变量 (static) 分配内存并设置默认初始值
  3. 解析:主要是将常量池中的符号引用替换为直接引用。

1.3.3 初始化阶段

  • 初始化阶段会执行静态代码块中的代码,并为静态变量赋值
    • 注意
      1. 直接访问父类的静态变量,不会触发子类的初始化。
      2. 子类的初始化 clinit 调用之前,会先调用父类的 clinit 初始化方法。

1.4 类加载器

  • 什么是类加载器(ClassLoader):

    • Java 虚拟机提供给应用程序去获取类和接口字节码数据的技术。

      类加载器只参与加载过程中的字节码获取并加载到内存这一部分。

1.4.1 类加载器的分类

  • 类加载器分为两类,一类是 Java 代码中实现的(扩展类加载器应用程序类加载器),一类是 Java 虚拟机底层源码实现的(启动类加载器,用 C++ 写的)。

    • 我们主要关注 Java 代码中实现的类加载器。
  • JDK 中默认提供了多种处理不同渠道的类加载器,程序员也可以自己根据需求定制。所有 Java 中实现的类加载器都需要继承 ClassLoader 这个抽象类。

1.4.2 ★双亲委派机制

  • 由于 Java 虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题

  • 双亲委派机制的作用:

    1. 保证类加载的安全性
      • 通过双亲委派机制避免恶意代码替换 JDK 中的核心类库,比如 java.lang.String,确保核心类库的完整性和安全性。
    2. 避免重复加载
      • 双亲委派机制可以避免同一个类被多次加载。
  • 双亲委派机制指的是:当一个类加载器接收到加载类的任务时,会先自底向上查找是否加载过,再由顶向下进行加载

    • 自底向上查找:每个类加载器都有一个父类加载器,在类加载的过程中,每个类加载器都会先检查是否已经加载了该类,如果已经加载则直接返回,否则会将加载请求委派给父类加载器。
  • 自顶向下加载:如果所有的父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试加载。起到一个加载优先级的作用。

1.4.3 打破双亲委派机制

自定义类加载器
  • 自定义类加载器并且重写 loadClass 方法,就可以将双亲委派机制的代码去除。

  • 我们首先来看看,双亲委派机制到底是在哪里实现的。

    • 先来分析 ClassLoader 的原理,ClassLoader 中包含了 4 个核心方法。

      • 双亲委派机制的核心代码就位于 loadClass 方法中

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        if (c == null) {
        long t0 = System.nanoTime();
        try {
        if (parent != null) {
        // 调用父类加载器的加载方法
        c = parent.loadClass(name, false);
        } else {
        c = findBootstrapClassOrNull(name);
        }
        } catch (ClassNotFoundException e) {
        }

        if (c == null) {
        // 父类加载器不能加载类,由自己这个类加载器来加载
        long t1 = System.nanoTime();
        c = findClass(name);
        }
        }
        • 通过自定义类加载器,改写上面的 loadClass 方法,就能实现打破双亲委派机制。
  • 自定义类加载器的默认父类加载器就是应用程序类加载器

  • 正常情况下,不应该去打破双亲委派机制。

    正确的去实现一个自定义类加载器的方式是重写 findClass 方法而不是 loadClass 方法,这样不会破坏双亲委派机制。

线程上下文类加载器
  • 利用上下文类加载器加载类,比如 JDBC 和 JNDI 等。

    • JDBC 中使用了 DriverManager 来管理项目中引入的不同数据库的驱动,比如 mysql 驱动、oracle 驱动。

      DriverManager 属于 rt.jar,是启动类加载器加载的,而用户 jar 包中的驱动需要由应用程序类加载器加载,这就违反了双亲委派机制,因为双亲委派机制应该是子类加载器委托父类加载器加载类,而这里则需要父类加载器委托子类加载器加载类。

      • DriverManage 是使用 SPI 机制,来加载 jar 包中对应的驱动类。SPI 中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。
    • 这种由启动类加载器加载的类,通过线程上下文类加载器(应用程序类加载器)去加载类的方式,打破了双亲委派机制。

---------------The End---------------