以下环境为 jdk1.8
两大类
分类 | 成员 | 语言 | 继承关系 |
引导类加载器 | bootstrap 引导类加载器 | C/C++ | 无 |
自定义类加载器 | extension 拓展类加载器、application 系统/应用类加载器、user define 用户自定义类加载器 | Java | 继承于 java.lang.ClassLoader |
四小类
Bootstrap 引导类加载器
负责加载以下路径中 jar 包里的类
public class BootstrapClassLoader {
public static void main(String[] args) {
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url.toExternalForm());
}
}
}
Extension 扩张类加载器
由 Bootstrap 加载,并将 Bootstrap 指定为 Extension 的父加载器
负责加载以下路径中 jar 包里的类
public class ExtensionClassLoader {
public static void main(String[] args) {
for (String path : System.getProperty("java.ext.dirs").split(";")) {
System.out.println(path);
}
}
}
Extension 还可以加载开发者放入 java.ext.dirs 中 jar 包里的类
Application/System 应用/系统类加载器
由 Bootstrap 加载,并将 Extension 指定为 Application 的父加载器
负责加载以下路径中 jar 包里的类
用户自己开发的类的 classpath,如 com.example.demo.XXXClass
package com.chen.main;
public class MyMain {
}
这个由用户创建的 com.chen.main.MyMain 类,就会由 Application 加载
为什么是由 Application 加载呢?这是因为类加载器的双亲委派(按下不表)
User Define 用户自定义类加载器
由 Application 加载,并将 Application 指定为 User Define 的父加载器
为什么要有 User Define 用户自定义加载器
我们写了这么久代码好像也没有使用过用户自定义类加载器啊,使用上面三种类加载器好像完全足够了。但是当有以下几种需求时,就需要使用用户自定义加载器来实现了:
- 隔离加载类
隔离 应用服务 与 框架 与 中间件 直接类的加载,防止可能存在相同路径的类发生冲突
- 修改类加载的方式
可以不使用 Extension 和 Application,让用户自定义类加载器在我们需要的时间点,加载我们所需的类
- 扩展加载源
.class 文件一般以本地、网络、压缩包(zip、jar、war)等方式加载
而用户自定义类加载器可以自己扩展加载源,比如在数据库、文件(jsp)、加密文件、各种电子设备中加载 .class 文件
- 防止源码泄露
我们写的 Java 程序生成的 .class 字节码文件很容易被反编译出源码。因此为了防止源码泄露,就需要给字节码加密,但是加密后的字节码就无法被 JVM 提供的类加载器加载了,需要我们自己实现能够加载加密字节码的类加载器
负责加载用户指定路径的类
双亲委派
什么是双亲,其实就是 parent 家长的意思,因此也可以理解为 "家长委派模式"
前面提到了 Extension 的父加载器是 Bootstrap、Application 的父加载器是 Extension,但是父加载器并不是父类的意思,他们中间不存在继承关系
所谓 "父加载器" 只是 ClassLoader 类中的一个属性,这个属性正是 parent
而 Extension 与 Application 都继承于 ClassLoader,因此都有这个属性
类加载流程
向上委派
当要一个类加载器要加载类的时候
- 当前的类加载器没有加载过该类
会先将加载任务提交给父加载器(即委派给双亲)。如果父加载器也有父加载器,那么加载任务会继续提交给父父加载器,直到没有父类加载器为止(即 parent 为 null),但是 parent 为 null 并不代表没有父类加载器,而是表示该加载器的父加载器为 Bootstrap 引导类加载器(因为 Bootstrap 是用 C/C++ 写的,并不是一个 Java 对象,无法存入 parent 属性中)。也就是说,最终的加载任务都会给到 Bootstrap 引导类加载器
- 当前的类加载器已经加载过该类
直接结束(最初加载器)/ 告知子加载器 "加载完成"(非最初加载器)
向下委派
Bootstrap
- 该类在 Bootstrap 类加载器负责的加载路径下
Bootstrap 完成该类的加载任务,并告知将任务委派给他的子加载器 "我搞定了"(完成该任务)
- 该类不在 Bootstrap 类加载器负责的加载路径下
Bootstrap 无法完成该类的加载任务,只能通知子加载器 "我搞不定,你自己试试看",将任务退回给子加载器(拒绝该任务)
子加载器
- 该类在子加载器负责的加载路径下
子加载器完成该类的加载任务,并告知将任务委派给他的子子加载器 "我搞定了"(完成该任务)
- 该类不在子加载器负责的加载路径下
子加载器无法完成该类的加载任务,只能通知子子加载器 "我搞不定,你自己试试看",将任务退回给子子加载器(拒绝该任务)
最初加载器
当最初加载器的所有祖宗都拒绝了最初加载器向上委派的类加载任务,它就只能自己来尝试加载了
- 该类在最初加载器负责的加载路径下
最初加载器完成该类的加载任务
- 该类不在子加载器负责的加载路径下
最初加载器无法完成该类的加载任务,由于它已经没有可以再向下委派的子加载器了,最终只能抛出 ClassNotFound 异常(在所有可加载的路径下,找不到这个类)
作用
避免重复加载类
比如 A 类和 B 类都有一个父类 C 类,那么当 A 启动时就会将 C 类加载起来,那么在 B 类进行加载时就不需要在重复加载 C 类了
确保程序安全性
使用双亲委派模型可以保证了 Java 的核心 API 不被篡改,如果没有使用双亲委派模型,而是每个类加载器自己加载就会出现一些问题。比如我们自己编写一个名为 java.lang.Object 类,那么程序运行的时候,AppClassLoader 就会加载这个 Object 类,如果我们写的 Object 类有问题,那么所有继承于 Object 类的类就会全部出错,整个程序直接崩溃 down 掉