ClassLoader+双亲委派模式

ClassLoad类加载器概述

​ ClassLoader的具体作用就是将class文件加载到jvm虚拟机中去,程序就可以正确运行了。

jvm启动的时候,并不会一次性加载所有的class文件,而是在程序的运行中动态的去加载。

通常Java中的.class文件会在以下两种情况被ClassLoader加载到内存中:

  • 调用类构造器
  • 调用类中的静态变量或静态方法

​ 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

​ 类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

JVM类加载机制

  • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

  • 父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

  • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

Java中的ClassLoader

  1. BootstrapClassLoader 系统类加载器
    BootstrapClassLoader并非java代码实现的,而是由C/C++代码编写的,它本身属于虚拟机的一部分,主要负责加载Java标准库的一些类,如 java.lang 包等,这些类通常位于 JDK 的 jre/lib 目录下。

  2. ExtClassLoader 扩展类加载器/PlatformClassLoader 平台类加载器
    负责加载Java扩展库的类,这些类通常位于JDK 的 jre/lib/ext目录下,在 Java 9 之后,由于模块化的引入,这个类加载器已经被废弃。平台类加载器负责加载 JDK 自带的模块。这些模块通常位于 JDK 安装目录下的 jmods 文件夹中

  3. APPClassLoader 系统类加载器
    AppClassLoader 主要加载系统属性“java.class.path”配置下类文件,也就是环境变量 CLASS_PATH 配置的路径。因此 AppClassLoader 是面向用户的类加载器,我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

双亲委派模式(Parents Delegation Model)

既然 JVM 中已经有了这 3 种 ClassLoader,那么 JVM 又是如何知道该使用哪一个类加载器去加载相应的类呢?答案就是:双亲委派模式。

双亲委派模式(Parent Delegation Model)是 Java 类加载器体系中的一种设计模式。它描述了类加载器在加载类时应如何与其父加载器(parent class loader)进行协作。双亲委派模式可以确保类在加载过程中的安全性和避免多次加载同一个类。

双亲委派模式的工作原理如下:

1.当一个类加载器(ClassLoader)被要求加载一个类时,它首先会将加载请求委托给其父类加载器。
2.父类加载器会递归地将加载请求继续委托给它自己的父类加载器,直到请求到达启动类加载器(Bootstrap ClassLoader)。
3.启动类加载器会检查它是否能够加载该类。如果可以加载,它将加载这个类并返回;否则,它会将加载请求传递回给子加载器。
4.子加载器会检查它是否能够加载该类。如果可以加载,它将加载这个类并返回;否则,它会将加载请求传递回给下一个子加载器。
5.如果所有加载器都无法加载该类,那么 ClassNotFoundException 将被抛出。

双亲委派模式的优势:

  • 避免类的重复加载:由于每个加载器只会尝试加载它的父加载器无法加载的类,这样可以确保同一个类不会被多次加载。
  • 保护 Java 核心类库的安全性:由于启动类加载器是第一个尝试加载类的加载器,用户自定义的类加载器无法加载 Java 核心类库中的类。这可以防止核心类库被恶意代码篡改或替换。
  • 有助于实现类的隔离:使用不同的类加载器可以实现类的隔离,这对于应用服务器等场景非常有用,因为它们需要隔离不同应用之间的类加载。

注意:“双亲委派”机制只是 Java 推荐的机制,并不是强制的机制。我们可以继承 java.lang.ClassLoader 类,实现自己的类加载器。如果想保持双亲委派模型,就应该重写 findClass(name) 方法;如果想破坏双亲委派模型,可以重写 loadClass(name) 方法。