类加载器(ClassLoader)在 Java 中的作用是什么?
在使用 Java 时,我们经常使用大量的类。 这些 Java 类不会一次全部加载到内存中,而是在应用程序需要时加载。 这就是 Java 类加载器发挥作用的地方。 因此,在本文中,我将结合示例讨论如何在 Java 中使用 ClassLoader。
本文将包含以下几个方面:
- 什么是类加载器?
- 类加载器的类型
- 类加载器原理
- 类加载器的方法
- 自定义类加载器
下面我们分别来看一下
什么是类加载器
Java 中的 ClassLoader 由 Java 运行环境调用,以便在 Java 虚拟机中的应用程序需要时动态加载类。 由于 ClassLoader 是 Java 运行环境的一部分,Java 虚拟机对底层文件和文件系统一无所知。
现在,让我们了解 Java 中不同类型的内置类加载器。
Java中类加载器的类型
Java中不同类型的类加载器如下:
- Extension 类加载器
- Application 或者 System 类加载器
- Bootstrap 类加载器
Extension 类加载器
顾名思义,Extension 类加载器从 JDK 扩展库加载核心 Java 类的扩展。 它是 Bootstrap 类加载器的子级,并从 JRE/lib/text 目录或 java.ext.dirs 系统属性中指定的任何其他目录加载扩展。
Application 或者 System 类加载器
Application 或者 System 类加载器是 Extension 类加载器的子级。 这种类型的类加载器加载在 -cp
命令行选项或 CLASSPATH
环境变量中找到的所有应用程序级别的类。
Bootstrap 类加载器
众所周知,Java 类是由 java.lang.ClassLoade
的实例加载的。 但是,由于类加载器是类,Bootstrap 类加载器负责加载 JDK 内部类。 BootStrap 类加载器是一个机器码,它在 JVM 调用它并从 rt.jar 加载类时启动操作。 因此,我们可以理解 Bootstrap 类加载器服务没有父类加载器,因此被称为 Primordial ClassLoader。
注意 : Bootstrap的优先级高于Extension,给Extension 类加载器的优先级高于Application 类加载器。 请参考下图:

接下来在本文中,让我们了解类加载器的工作原理。
类加载器原理
Java 类加载器工作所依据的一组规则是以下三个原则:
- 唯一性属性
- 委托模型
- 可见性原则
唯一性属性
此属性确保没有类的重复,并且所有类都是唯一的。 唯一性属性还确保由父类加载器加载的类不会由子类加载器加载。 在父类加载器找不到类的情况下,当前实例将尝试自行完成。
委托模型
Java 中的类加载器基于委托模型给出的一组操作工作。因此,每当生成查找类或资源的请求时,ClassLoader 实例会将类或资源的搜索委托给父类加载器。
ClassLoader 工作所基于的操作集如下:
- 每当遇到一个类时,Java 虚拟机都会检查该类是否已加载。
- 在加载类的情况下,JVM 继续执行类,但在未加载类的情况下,JVM 要求 Java 类加载器子系统加载该特定类。之后,类加载器子系统将控制权交给 Application 类加载器。
- 然后 Application 类加载器将请求委托给 Extension 类加载器,Extension 类加载器随后将请求传递给 Bootstrap 类加载器。
- 现在,Bootstrap 类加载器在 Bootstrap 类路径中搜索以检查该类是否可用。如果类可用,则加载它,否则请求再次传递给 Extension 类加载器。
- Extension 类加载器检查扩展类路径中的类。如果类可用,则加载它,否则请求再次传递给 Application 类加载器。
- 最后,Application 类加载器在 Application 类路径中搜索类。如果类可用,则加载,否则将抛出 ClassNotFoundException 异常。

可见性原则
根据这个原则,子类对其父类加载器加载的类是可见的,反之则不成立。 因此,Application 类加载器加载的类可以看到 Extension 和 Bootstrap 类加载器加载的类。
例如,如果我们有两个类:A 和 B,假设 A 类由 Application 类加载器加载,B 类由 Extensions 类加载器加载。 这里,类 A 和 B 对 Application 类加载器加载的所有类可见,但类 B 仅对 Extension 类加载器加载的那些类可见。
此外,如果我们尝试使用 Bootstrap 类加载器加载这些类,将看到 java.lang.ClassNotFoundException 异常。
好了,既然我们已经了解了类加载器的类型及其背后的原理,那么让我们来看看 java.lang.ClassLoader
类中的几个重要方法。
Java 类加载器的方法
类加载器的几个基本方法如下:
- loadClass(String name, boolean resolve)
- defineClass()
- findClass(String name)
- Class.forName(String name, boolean initialize, ClassLoader loader)
- getParent()
- getResource()
loadClass(String name, boolean resolve)
该方法是类加载器的入口点,用于加载JVM引用的类。 它将类的名称作为参数。 JVM 调用 loadClass() 方法,通过将布尔值设置为 true 来解析类引用。 只有当我们需要确定类是否存在时,布尔参数才设置为 false。
其定义如下
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
defineClass()
用于将字节数组定义为类实例的最终方法。 如果该类无效,则会引发 ClassFormatError。
其定义如下
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError
findClass(String name)
findClass 方法用于查找指定的类。 因此,它只是查找具有完全限定名称的类作为参数,但不加载该类。 如果父类加载器找不到请求的类,则 loadClass() 方法调用此方法。 此外,如果类加载器的父级没有找到该类,则默认实现会抛出 ClassNotFoundException
。
其定义如下
protected Class<?> findClass(String name) throws ClassNotFoundException
Class.forName(String name, boolean initialize, ClassLoader loader)
此方法用于加载和初始化类。 它提供了选择任何类加载器的选项,如果类加载器参数为 NULL,则自动使用 Bootstrap 类加载器。
其定义如下
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)throws ClassNotFoundException
getParent()
getParent 方法用于返回父类加载器进行委托。
其定义如下
public final ClassLoader getParent()
getResource()
顾名思义,getResource() 方法试图找到具有给定名称的资源。 它最初会将请求委托给资源的父类加载器。 如果 parent 为 null,则搜索 JVM 内置的类加载器的路径。 现在,如果这失败了,那么该方法将调用 findResource(String) 来查找资源,其中资源名称被指定为可以是绝对或相对类路径的输入。 然后,它返回一个用于读取资源的 URL 对象,或者如果资源没有足够的权限来返回资源或未找到,则返回空值。
其定义如下
public URL getResource(String name)
接下来,在这篇关于Java中的类加载器的文章中,让我们了解一下自定义类加载器。
自定义类加载器
内置的类加载器将处理文件已经在文件系统中的大多数情况,但如果想从本地硬盘加载类,那么我们需要使用自定义类加载器。
创建类加载器
要创建自定义类加载器,需要继承 ClassLoader
类并覆盖 findClass()
方法:
示例:让我们创建一个自定义类加载器,它继承了默认 ClassLoader 并从指定文件加载字节数组。 看下面的代码。
package jiyik;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class Sample extends ClassLoader {
@Override
public Class findClass(String samplename) throws ClassNotFoundException {
byte[] b = customLoadClassFromFile(samplename);
return defineClass(samplename, b, 0, b.length);
}
private byte[] customLoadClassFromFile(String demofilename) {
InputStream inStream = getClass().getClassLoader().getResourceAsStream(
demofilename.replace('.', File.separatorChar) + ".class");
byte[] buffer;
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
int nextValue = 0;
try {
while ( (nextValue = inStream.read()) != -1 ) {
bStream.write(nextValue);
}
} catch (IOException e) {
e.printStackTrace();
}
buffer = bStream.toByteArray();
return buffer;
}
}
至此,我们就结束了这篇关于 Java 中的类加载器的文章。
相关文章
Do you understand JavaScript closures?
发布时间:2025/02/21 浏览次数:108 分类:JavaScript
-
The function of a closure can be inferred from its name, suggesting that it is related to the concept of scope. A closure itself is a core concept in JavaScript, and being a core concept, it is naturally also a difficult one.
Do you know about the hidden traps in variables in JavaScript?
发布时间:2025/02/21 浏览次数:178 分类:JavaScript
-
Whether you're just starting to learn JavaScript or have been using it for a long time, I believe you'll encounter some traps related to JavaScript variable scope. The goal is to identify these traps before you fall into them, in order to av
How much do you know about the Prototype Chain?
发布时间:2025/02/21 浏览次数:150 分类:JavaScript
-
The prototype chain can be considered one of the core features of JavaScript, and certainly one of its more challenging aspects. If you've learned other object-oriented programming languages, you may find it somewhat confusing when you start
如何在 JavaScript 中合并两个数组而不出现重复的情况
发布时间:2024/03/23 浏览次数:86 分类:JavaScript
-
本教程介绍了如何在 JavaScript 中合并两个数组,以及如何删除任何重复的数组。