类加载器
尽管在 Java 开发中无须过分关心类加载机制,但所有的编程人员都应该了解其工作机制。
1. 类加载器
类加载器负责将 .class
文件加载到内存中,并为之生成对应的 java.lang.Class
对象。
Java 类加载器除了根类加载器之外,其他类加载器都是使用 Java 语言编写的,所以程序员完全可以开发自己的类加载器。
在 Java 中,一个类用其全限定类名作为标识;但在 JVM 中,一个类用其全限定类名和其类加载器作为唯一标识。
当 JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构:
加载器 | 说明 | 作用 |
---|---|---|
Bootstrap ClassLoader | 根类加载器 | 负责加载 Java 的核心类 |
Extension ClassLoader | 扩展类加载器 | |
System ClassLoader | 系统类加载器 |

import java.io.IOException;
import java.net.URL;
// Enumeration这种传统接口已被迭代器取代
// 虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。
import java.util.Enumeration;
class ClassLoaderPropTest {
public static void main(String[] args) throws IOException {
// 获取系统类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:" + systemLoader);
Enumeration<URL> eml = systemLoader.getResources("");
while (eml.hasMoreElements()) {
System.out.println(eml.nextElement());
}
ClassLoader extensionLoader = systemLoader.getParent();
System.out.println("扩展类加载器:" + extensionLoader);
System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的parent:" + extensionLoader.getParent());
}
/*
* 结果输出为:
* 系统类加载器:jdk.internal.loader.ClassLoaders$AppClassLoader@5a2e4553
* file:/C:/Users/Xuchuan/AppData/Roaming/Code/User/workspaceStorage/
* 0619d618e4bae95f674d3adaa556f7c3/redhat.java/jdt_ws/tmp_693fe676/bin/
* 扩展类加载器:jdk.internal.loader.ClassLoaders$PlatformClassLoader@5577140b
* 扩展类加载器的加载路径:null
* 扩展类加载器的parent:null
*/
}
2. 类加载机制
JVM 的类加载机制主要有如下三种:
- 全盘负责:当一个类加载器加载某个
Class
时,该Class
所依赖的和引用的其他Class
也将由该类加载器负责载入,除非显式使用另外一个类加载器载入; - 父类委托:先让父类加载器试图加载该
Class
,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类(类加载器之间的父子关系并不是类继承上的父子关系,而是类加载器实例之间的关系); - 缓存机制:所有加载过的
Class
都会被缓存,当程序中需要使用某个Class
时,类加载器先从缓存区中搜寻该Class
,只有当缓存区中不存在该Class
对象时,系统才会读取该类对应的二进制数据,并将其转换成Class
对象,存入缓存区中。这也是为什么修改了Class
后必须重新启动 JVM,程序所做的修改才会生效的原因。
3. 自定义类加载器
ClassLoader
是 java.lang
包下的一个抽象类,JVM 中除根类加载器之外的所有类加载器都是 ClassLoader
子类的实例,开发者可以通过扩展 ClassLoader
类来实现自定义的类加载器。
ClassLoader
抽象类有如下几个关键方法(通常推荐重写findClass()
方法而不是loadClass()
方法):方法 作用 loadClass(String name, boolean resolve)
该方法为 ClassLoader
的入口点,根据指定名称加载类,系统就是调用ClassLoader
的该方法来获取指定类对应的Class
对象findClass(String name)
根据指定名称来查找类 Class defineClass(String name, byte[] b, int off, int len)
将指定类的字节码文件,即 Class
文件,读入字节数组byte[] b
内,并把它转换为Class
对象ClassLoader
抽象类还有如下一些普通方法:方法 作用 findSystemClass(String name)
从本地文件系统装入文件。它在本地文件系统中寻找类文件,若存在,就使用 defineClass
方法将原始字节转换成Class
对象,以将该文件转换成类static getSystemClassLoader()
返回系统类加载器 getParent()
获取该类加载器的父类加载器 resolveClass(Class<?> c)
链接指定的类。类加载器可以使用此方法来链接类 c
findLoadedClass(String name)
若此 JVM 已载了名为 name
的类,则直接返回该类对应的Class
实例,否则返回null
。该方法是 Java 类加载缓存机制的体现。
实际上,使用自定义的类加载器,可以实现如下常见功能:
- 执行代码前自动验证数字签名;
- 根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译
*.class
文件; - 根据用户需求来动态地加载类;
- 根据应用需求把其他数据以字节码的形式加载到应用中。
4. URLClassLoader类
Java 为 ClassLoader
抽象类提供了一个 URLClassLoader
实现类,该类也是系统加载器和扩展类加载器的父类。
URLClassLoader
的构造器:
URLClassLoader(URL[] urls)
:使用默认的父类加载器创建一个ClassLoader
对象,该对象将从urls
所指定的系列路径来查询并加载类。URLClassLoader(URL[] urls, ClassLoader parent)
:使用指定的父类加载器创建一个ClassLoader
对象,其他功能与上相同。
对于上述的 URL
而言:
- 可以以
file:
为前缀,表明从本地文件系统加载; - 可以以
http:
为前缀,表明从互联网通过 HTTP 访问来加载; - 可以以
ftp:
为前缀,表明从互联网通过 FTP 访问来加载; - ......