类加载器整理解析

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类加载的懒加载(或称为延迟加载)特性,即类只在需要时才被加载,这种机制有助于减少程序启动时的内存占用和加载时间。

// 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)上的类。

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)。

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.使用类加载器来加载类

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

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

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值