什么是类加载器?
类加载器是Jvm的重要组成之一(类加载器、运行时数据区、执行引擎、本地库接口、本地方法库),负责读取java字节码并将其加载到Jvm中的组件
类加载器的分类
Java中的类加载器可以分为以下几种:
1. 启动类加载器(Bootstrap ClassLoader)
- 定义 :这是最顶层的类加载器,用于加载Java核心类库(如
java.lang.*
,java.util.*
等)。 - 实现:启动类加载器是由本地代码(通常是C/C++)实现的,并不是一个Java类。
- 加载路径 :它加载位于
<JAVA_HOME>/lib
目录下的类文件。
2. 扩展类加载器(Extension ClassLoader)
- 定义:用于加载扩展类库(通常是标准类库的扩展)。
- 实现 :由
sun.misc.Launcher$ExtClassLoader
类实现。 - 加载路径 :它加载位于
<JAVA_HOME>/lib/ext
目录下的类文件,或者由java.ext.dirs
系统属性指定的路径。
3. 应用程序类加载器(Application ClassLoader)
- 定义:又称为系统类加载器(System ClassLoader),用于加载应用程序的类文件。
- 实现 :由
sun.misc.Launcher$AppClassLoader
类实现。 - 加载路径 :它加载由
CLASSPATH
环境变量或java.class.path
系统属性指定的路径中的类文件。
4. 自定义类加载器(Custom ClassLoader)
- 定义:自定义类加载器是由开发者定义的类加载器,用于从特定来源加载类文件。通过自定义类加载器,可以实现从文件系统、网络、数据库等非标准路径加载类文件。
- 实现 :自定义类加载器通常通过继承
java.lang.ClassLoader
类并重写其方法来实现。例如,重写findClass
方法来定义类的加载逻辑。 - 加载路径:自定义类加载器可以从任何指定的路径加载类文件,这取决于自定义的实现逻辑。
类加载器的双亲委派模型
Java采用了双亲委派模型(Parent Delegation Model)来管理类加载器的层次结构。在这种模型中,每个类加载器在尝试加载类之前,会先委派给它的父类加载器加载。只有当父类加载器无法找到该类时,子加载器才会尝试加载。
例如当一个自定义类加载器试图加载一个类,它会先委派应用程序类加载器,如何应用程序类加载器会委派扩展类加载器,扩展类加载器会委派启动类加载器,启动类加载器先看一下自己有没有这个类,没有就让扩展类去加载,有的话直接自己加载,同理,扩展类也会先看有没有,没有的话让应用程序类加载,应用程序类也是同理。假如所有类加载器都没有该类,则会抛出ClassNotFoundException异常。
如图,自定义类加载器的父类加载器通常是应用程序类加载器(可改),应用程序的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器。值得一提的是,父类加载器是用类种的一个成员变量(parent)存储的,而因为启动类加载器特俗的地位和职责,无法在java中获取到,所以实际上扩展类加载器的parent存放的是一个null,实际需要调用启动类加载器时会通过特殊的方法实现。通过getClassLoader()方法去获得启动类加载器返回的也是null。
双亲委派模型的好处
- 安全性:防止核心类库被自定义类覆盖,提高系统的安全性。
- 避免重复加载:确保类只被加载一次,提高性能和内存利用率。
打破双亲委派机制的三种方法
一、自定义类加载器
自定义一个类加载,重写loadClass()方法,清除双亲委派机制的代码,实例:
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 检查类是否已经加载
Class<?> c = findLoadedClass(name);
// 如果类未被加载,尝试加载类
if (c == null) {
try {
// 自定义类加载逻辑
c = findClass(name);
} catch (ClassNotFoundException e) {
// 如果找不到类,再尝试使用父类加载器加载
if (getParent() != null) {
c = getParent().loadClass(name);
} else {
throw new ClassNotFoundException(name);
}
}
}
// 如果需要链接类
if (resolve) {
resolveClass(c);
}
return c;
}
二、利用线程上下文类加载器
调用线程上下文类加载器(通常是应用程序类加载器)来打破双亲委派机制,思路是当一个类的类加载器是启动类或扩展类加载器时,是无法通过双亲委派机制去委派到应用程序类加载器的,假如需要加载的类又只有应用程序类加载器中有,那么此时就可以通过线程上下文类加载器去加载,因为线程上下文类加载器通常保存的是应用程序类加载器。
// 获取当前线程的上下文类加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
利用线程上下文类加载器到底有没有打破双亲委派机制?
在笔者看来是没有直接打破双亲委派机制的,因为线程上下文类加载器依然是按照双亲委派机制去加载类,线程上下文类加载器只是提供了一种更灵活的方式去加载类。
三、OSGI框架的类加载器
OSGI框架自己实现了一套类加载机制打破了双亲委派机制,在OSGI中允许同级类互相委派,并且OSGI还使用类加载器实现了项目的热部署。
总结
类加载器:加载 Java 类字节码到 JVM 的组件,负责类的查找、加载和连接。
类加载器的分类:
- 启动类加载器(Bootstrap ClassLoader):加载核心 Java 类库。
- 扩展类加载器(Extension ClassLoader):加载扩展类库。
- 应用类加载器(Application ClassLoader):加载应用程序类。
双亲委派机制:类加载请求先递交给父类加载器处理,避免重复加载和确保核心类安全。
打破双亲委派机制的三种方法:
- 自定义类加载器:自定义逻辑加载类,绕过父类加载。
- 线程上下文类加载器:通过修改线程的上下文类加载器,动态选择类加载器。
- 使用 OSGi 框架:OSGi 自带的类加载机制支持模块化和隔离,突破双亲委派。