类加载器整理解析

1.为什么要有类加载器的设计?

在不使用类加载器的场景中(假想场景),所有的类 都是在程序启动时一次性加载到JVM中的。

(1)程序启动慢 。需要在启动时一次性加载程序中的所有类,即使在程序运行期间根本用不到。

(2)资源浪费 。未被使用的类的加载占用了JVM的内存和其他资源。

(3)类冲突 。在复杂的应用程序中,可能会存在多个版本的同一个类库 。如果不使用类加载器进行隔离,这些不同版本的类库可能会相互冲突,导致程序运行错误。(所有的类都将处于同一个命名空间 中,这会导致版本冲突和命名冲突)

(4)安全性问题 。一次性加载所有类的方式缺乏灵活性,无法根据类的来源、用途等信息进行有针对性的安全检查。如果采用一次性加载所有类的方式,当所有类都被加载到内存中时,恶意代码也可能已经混入其中。

2.什么是类加载器?

Java类加载器(Class Loader)是Java运行时环境 (JRE)的一部分 ,负责动态地 将Java类加载到Java虚拟机(JVM)的运行时环境中。当一个Java程序需要使用某个类时,JVM并不会立即去加载这个类,而是等到程序真正运行时,第一次引用到这个类的时候,才会通过类加载器来加载这个类。

类加载器必须实现的核心功能 是通过全类名 获取类的二进制字节流,并将类的二进制字节流解析并转换成JVM能够识别的数据结构(如Java类和接口的内部表示,以及类的元数据信息等),然后存储在JVM的方法区中,最后在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

下面的例子展示了Java类加载的懒加载(或称为延迟加载)特性,即类只在需要时才被加载,这种机制有助于减少程序启动时的内存占用和加载时间。

java 复制代码
// MyClass.java  
public class MyClass {  
    public void sayHello() {  
        System.out.println("Hello, MyClass!");  
    }  
}  
  
// MainClass.java  
public class MainClass {  
    public static void main(String[] args) {  
        // 在这里,我们还没有创建MyClass的实例或调用其方法  
        // 因此,根据懒加载的原则,MyClass类还没有被加载到JVM中  
  
        // 假设这里有一些其他的逻辑...  
  
        // 当我们第一次引用MyClass时(例如,通过创建其实例或调用其静态方法),  
        // JVM会通过类加载器来加载MyClass类  
        MyClass myObject = new MyClass();  
        myObject.sayHello(); // 在这里,MyClass类被加载到JVM中,并执行sayHello方法  
  
        // 从现在起,MyClass类已经在JVM中,可以被多次使用而无需重新加载  
    }  
}

3.类加载器的实现

3.1三种类加载器:

(1)启动类加载器(Bootstrap ClassLoader)

C/C++语言实现 ,是JVM自带的类加载器,负责加载Java的核心库(如java.lang、java.util等),

(2) 扩展类加载器(Extension ClassLoader)

由Java语言编写,负责加载Java的扩展库 ,是ClassLoader类的子类

(3) 系统类加载器(System ClassLoader)

别名:应用程序类加载器(Application ClassLoader),是ClassLoader类的子类,负责加载用户类路径(CLASSPATH)上的类库(包括开发者自己编写的类以及第三方库)

3.2三种类加载器之间的组织关系:

(1)逻辑关系

启动类加载器 是JAVA中所有类加载器的最顶层父加载器,但出于安全考虑,它并不继承自java.lang.ClassLoader,因此没有父加载器的概念;扩展类加载器 的父加载器是启动类加载器;
系统类加载器的父加载器是扩展类加载器。

(2)什么是父加载器

在Java的类加载机制中,"父加载器"是一个相对的概念,它指的是在类加载器的双亲委派模型 (Parent Delegation Model)中,当前类加载器用于委托加载类请求的上一级类加载器。

值得注意的是:类加载器的父加载器是一种包含或委托关系 (这种关系通过双亲委派模型来实现),而不是Java类之间的继承关系 (子加载器并不是父加载器的子类,它们之间的关系是通过委托和组合来实现的,而不是通过继承)。

(3)什么是双亲委派模型(重要面试题)

当Java程序需要使用 某个类时,负责加载这个类的类加载器会首先 把加载请求委托给它的父加载器 ,而不是自己直接加载,这种机制被称为双亲委派模型。

向上委派:实际上是查找缓存 ,是否加载了该类,有则直接返回,没有继续向上。

向下查找:查找加载路径 ,有则加载返回,没有则继续向下查找。

代码示例:

代码首先获取了ClassLoaderDemo类的类加载器,并打印出来,这通常是Application ClassLoader(系统类加载器),它负责加载用户路径(如CLASSPATH)上的类。

java 复制代码
public class ClassLoaderDemo {  
    public static void main(String[] args) {  
        // 打印当前类的加载器  
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();  
        System.out.println("ClassLoaderDemo的类加载器是:" + classLoader);  
  
        // 获取类加载器的父加载器,并继续向上获取直到为null(Bootstrap ClassLoader)  
        while (classLoader != null) {  
            classLoader = classLoader.getParent();  
            System.out.println("父类加载器是:" + classLoader);  
        }  
  
        // 注意:上面的循环最终会打印null,因为Bootstrap ClassLoader没有父加载器  
        // Bootstrap ClassLoader是JVM自带的类加载器,负责加载Java的核心类库  
  
        // 尝试加载java.lang.String类,观察其加载器  
        ClassLoader stringClassLoader = String.class.getClassLoader();  
        System.out.println("java.lang.String的类加载器是:" + stringClassLoader);  
  
        // 通常,核心类(如java.lang.String)是由Bootstrap ClassLoader加载的  
        // Bootstrap ClassLoader不是java.lang.ClassLoader的子类,因此返回null  
    }  
}

通过循环向上获取类加载器的父加载器,直到达到Bootstrap ClassLoader(启动类加载器),Bootstrap ClassLoader不是java.lang.ClassLoader的子类,因此调用getParent()会返回null。由于java.lang.String是Java的核心类,尝试获取其类加载器,并打印出来,会发现是由Bootstrap ClassLoader加载的,而Bootstrap ClassLoader不是java.lang.ClassLoader的子类,所以调用getClassLoader()会返回null。

(4)为什么需要双亲委派模型(委托加载的好处)

(1)确保Java平台的核心类库的安全性 (例如,无论哪个类加载器尝试加载java.lang.Object类,最终都会由启动类加载器来加载,从而避免了被恶意代码替换的风险)

(2)有助于实现类的唯一性,无论通过哪个类加载器加载,同一个类在JVM中只会有一个Class对象,这有助于维护Java的类型安全。

4.怎么用类加载器

4.1.获取类加载器

(1)获取系统类加载器(Application ClassLoader):这是默认的类加载器,通过ClassLoader.getSystemClassLoader()获取

(2)获取当前类的类加载器

任何类都可以通过调用getClass().getClassLoader()来获取加载该类的类加载器

(3)获取父加载器

通过调用类加载器的getParent()方法可以获取其父类加载器(如果有的话),注意,Bootstrap ClassLoader(启动类加载器)没有父类加载器,因此会返回null。

4.2.自定义类加载器

如果需要自定义类加载行为(如从非标准路径加载类、加密的类文件 等),可以继承java.lang.ClassLoader类并重写其findClass(String name)方法(或loadClass(String name, boolean resolve)方法,但通常只重写findClass)。

java 复制代码
public class MyClassLoader extends ClassLoader {  
    @Override  
    public Class<?> findClass(String name) throws ClassNotFoundException {  
        // 自定义加载逻辑,例如从文件系统、网络等位置加载类  
        byte[] classData = loadClassData(name); // 假设这个方法实现了从某处加载类数据的逻辑  
        return defineClass(name, classData, 0, classData.length);  
    }  
  
    private byte[] loadClassData(String name) {  
        // 实现加载类数据的逻辑  
        // ...  
        return null; // 示例,实际应返回类数据的字节数组  
    }  
}
4.2.使用类加载器来加载类

一旦有了类加载器(无论是系统类加载器、当前类的类加载器还是自定义类加载器),使用其来加载类

java 复制代码
ClassLoader classLoader = MyClassLoader.class.getClassLoader(); // 获取MyClassLoader的类加载器,或直接使用自定义的MyClassLoader实例  
Class<?> clazz = classLoader.loadClass("com.example.MyClass"); // 加载指定全限定名的类  
  
// 或者,如果是自定义类加载器  
MyClassLoader myClassLoader = new MyClassLoader();  
Class<?> clazz = myClassLoader.loadClass("com.example.MyClass");

参考视频资料:https://www.bilibili.com/video/BV1HP411t7Lq?p=16\&vd_source=ea0f09fc475a05984bf43c87396aa0f8

相关推荐
羊十一23 分钟前
C++(C++的文件I/O)
开发语言·c++·cocoa
来一杯龙舌兰26 分钟前
【JAVA】自动生成常量类、自动生成所需代码(附源码)
java·开发语言·c#·自动生成代码
Flying_Fish_roe31 分钟前
Spring Boot-依赖冲突问题
java·linux·spring boot
Am心若依旧40932 分钟前
[C++进阶[六]]list的相关接口模拟实现
开发语言·数据结构·c++·算法·list
月临水36 分钟前
JavaEE:网络编程(套接字)
java·网络·java-ee
国通快递驿站36 分钟前
AntFlow系列教程二之流程同意
java·开发语言
GuangdongQi1 小时前
pm2 进程守护python flask
开发语言·python·flask
宏基骑士1 小时前
【java面向对象二】static(一)
java·开发语言
IT学长编程1 小时前
计算机毕业设计 基于SpringBoot框架的网上蛋糕销售系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·毕业论文·计算机毕业设计选题·计算机毕业设计开题报告·网上蛋糕销售系统
尘浮生1 小时前
Java项目实战II基于Java+Spring Boot+MySQL的服装厂服装生产管理系统的设计与实现
java·开发语言·spring boot·后端·mysql·maven·intellij-idea