1. 类加载器 ClassLoader
的作用是
- 类加载器
ClassLoader
就是加载其他类的类 - 类加载器负责将字节码文件加载到内存,创建
Class
对象
2. 自定义 ClassLoader
可以实现哪些功能
- 热部署。在不重启 Java 程序的情况下,动态替换类的实现,比如 Java Web 开发中的 JSP 技术就利用自定义的
ClassLoader
实现修改 JSP 代码即生效;OSGI(Open Service Gateway Initiative) 框架使用自定义的ClassLoader
实现动态更新 - 应用的模块化和互相隔离。不同的
ClassLoader
可以加载相同的类但互相隔离、互不影响。Web 应用服务器如 Tomcat 利用这一点在一个程序中管理多个 Web 应用程序,每个 Web 应用使用自己的ClassLoader
,这些 Web 应用互不干扰。OSGI 和 Java 9 利用这一点实现了一个动态模块化架构,每个模块有自己的ClassLoader
,不同模块可以互不干扰 - 从不同地方灵活加载。系统默认的
ClassLoader
一般从本地的 .class 文件或 jar 文件中加载字节码文件,通过自定义的ClassLoader
,我们可以从共享的 Web 服务器、数据库、缓存服务器等其他地方加载字节码文件
3. 类加载的基本机制
- 运行 Java 程序,就是执行 java 这个命令,指定包含
main()
方法的完整类名,以及一个classpath
,即类路径。类路径可以有多个,对于直接的 class 文件,路径是 class 文件的根目录;对于 jar 包,路径是 jar 包的完整名称(包括路径和 jar 包名) - Java 运行时,会根据类的完全限定名寻找并加载类,寻找的方式基本就是在系统类和指定的类路径中寻找。如果是 class 文件的根目录,则直接查看是否有对应的子目录及文件;如果是 jar 文件,则首先在内存中解压文件,然后再查看是否有对应的类
- 负责加载类的类就是类加载器,它的输入是完全限定的类名,输出是
Class
对象。类加载器不是只有一个,一般程序运行时,都会有三个(适用于 Java 9 之前,Java 9 引入了模块化,基本概念是类似的,但有一些变化)
4. Java 程序在运行时,三个类加载器分别是
启动类加载器(Bootstrap ClassLoader)
- 这个加载器是 JVM 实现的一部分,不是 Java 语言实现的,一般是 C++ 实现的
- 它负责加载 Java 的基础类,主要是
<JAVA_HOME>/lib/rt.jar
,我们日常用的 Java 类库比如 Spring、ArrayList
等都位于该包内
扩展类加载器(Extension ClassLoader)
- 这个加载器的实现类是
sun.misc.Launcher$ExtClassLoader
- 它负责加载 Java 的一些扩展类,一般是
<JAVA_HOME>/lib/ext
目录中的 jar 包
- 这个加载器的实现类是
应用程序类加载器(Application ClassLoader)/系统类加载器(System ClassLoader)
- 这个加载器的实现类是
sun.misc.Launcher$AppClassLoader
- 它负责加载应用程序的类,包括自己写的和引入的第三方的类库,即所有在类路径中指定的类
- 这个加载器的实现类是
5. 这三个类加载器的关系是
- 这三个类加载器有一定的关系,可以认为是父子关系
Application ClassLoader
的父亲是Extension ClassLoader
,Extension ClassLoader
的父亲是Bootstrap ClassLoader
- 注意不是父子继承关系,而是父子委派关系。子
ClassLoader
有一个变量parent
指向父ClassLoader
,在子ClassLoader
加载类时,一般会首先通过父ClassLoader
加载
6. 类加载的基本过程是
- 判断是否已经加载过了,如果加载过了,直接返回
Class
对象。一个类只会被一个ClassLoader
加载一次 - 如果没有被加载,先让父
ClassLoader
去加载,如果加载成功,返回得到的Class
对象 - 在父
ClassLoader
没有加载成功的前提下,自己尝试加载类 - 一个程序运行时,会创建一个
Application ClassLoader
,在程序中用到ClassLoader
的地方,如果没有指定,一般用的都是这个ClassLoader
。所以,这个ClassLoader
也被称为系统类加载器(System ClassLoader
)
7. 类加载过程又称为什么模型
- 双亲委派模型:即优先让父
ClassLoader
去加载
8. 为什么要先让父 ClassLoader
去加载呢
- 这样可以避免 Java 类库被覆盖的问题,保证类加载的唯一性和一致性
- 比如,用户程序也定义了一个类
java.lang.String
,通过双亲委派,java.lang.String
只会被Bootstrap ClassLoader
加载 - 这就避免了自定义的
String
覆盖 Java 类库的定义
- 比如,用户程序也定义了一个类
9. 双亲委派虽然是一般模型,但也有一些例外,比如说
- 自定义的加载顺序:尽管不被建议,自定义的
ClassLoader
可以不遵从双亲委派这个约定。不过,即使不遵从,以 java 开头的类也不能被自定义类加载器加载,这是由 Java 的安全机制保证的,以避免混乱 - 网状加载顺序:在 OSGI 框架和 Java 9 模块化系统中,类加载器之间的关系是一个网,每个模块有一个类加载器,不同模块之间可能有依赖关系。在一个模块加载一个类时,可能是从自己模块加载,也可能是委派给其他模块的类加载器加载
- 父加载器委派给子加载器加载:典型的例子有 JNDI(Java Naming and Directory Interface) 服务,它是 Java 企业级应用中的一项服务