在Java中,类加载器(Class Loaders)是用来动态加载Java类到Java虚拟机(JVM)中的一部分。它们在运行时将.class
文件中的字节码转换为Class
对象。Java平台使用委派模型(Delegation Model)来加载类,这使得类加载器可以按需加载类,而不是在启动时一次性加载所有类。
类加载器的类型
Java中的类加载器主要分为以下几种类型:
-
启动类加载器(Bootstrap Class Loader)
- 这是类加载器层次结构中的最顶层加载器,用于加载Java运行时环境(JRE)的核心类库,如
rt.jar
和其他核心库。 - 它是由本地代码实现的,比如C++。
- 它不是Java的一部分,不继承自
java.lang.ClassLoader
。
- 这是类加载器层次结构中的最顶层加载器,用于加载Java运行时环境(JRE)的核心类库,如
-
扩展类加载器(Extension Class Loader)
- 这个加载器负责加载JAVA_HOME/lib/ext目录中或者由系统属性
java.ext.dirs
指定位置中的类库。 - 它是由
sun.misc.Launcher$ExtClassLoader
(Sun的JDK)实现的。
- 这个加载器负责加载JAVA_HOME/lib/ext目录中或者由系统属性
-
应用程序类加载器(Application Class Loader)
- 这个加载器负责加载环境变量classpath或系统属性
java.class.path
指定路径中的类库。 - 这是类加载器层次结构中的默认加载器,通过
ClassLoader.getSystemClassLoader()
方法可以获得它的实例。 - 它通常是用户自定义类加载器的父加载器。
- 这个加载器负责加载环境变量classpath或系统属性
-
用户自定义类加载器(User-Defined Class Loaders)
- Java开发者可以通过继承
java.lang.ClassLoader
类的方式创建自己的类加载器。 - 用户自定义的类加载器通常用来加载特定源的类,如从网络、加密文件等。
- Java开发者可以通过继承
类加载器的工作原理
类加载器的工作机制遵循三个主要原则:
-
委派模型(Delegation Model)
- 当类加载器收到类加载的请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
- 只有当父类加载器无法完成这个加载请求(它在加载路径上未找到所需类)时,子类加载器才会尝试自己去加载这个类。
-
可见性(Visibility)
- 子类加载器可以访问父类加载器加载的类。这意味着Java类可以在不同的加载器间共享。
-
唯一性(Uniqueness)
- 由父类加载器加载的类不能被子类加载器重新加载。一旦一个类被加载到JVM中,它将维持加载器的命名空间,这确保了Java类在JVM中的唯一性。
类加载器的操作
类加载器的主要操作是loadClass
方法,它遵循以下步骤:
-
检查请求的类是否已经被加载
- 类加载器首先检查请求加载的类是否已经被加载过了,如果是,则返回已经加载的
Class
对象。
- 类加载器首先检查请求加载的类是否已经被加载过了,如果是,则返回已经加载的
-
委派给父类加载器
- 如果类没有被加载,类加载器将请求委派给父类加载器。
-
加载类
- 如果父类加载器无法加载请求的类,子类加载器尝试自己去加载。
- 加载过程包括查找字节码并将其实例化为
Class
对象。
类加载的过程
类加载的过程包括以下阶段:
-
加载(Loading)
- 将类的
.class
文件读入内存,并为之创建一个java.lang.Class
对象。
- 将类的
-
链接(Linking)
- 验证(Verification):确保加载的类符合JVM规范,没有安全问题。
- 准备(Preparation):为类变量(静态变量)分配内存,并设置默认初始值。
- 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用。
-
初始化(Initialization)
- 执行类构造器
<clinit>()
方法的过程。这个方法由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而成。 - 在这个阶段,如果有父类,会先初始化父类。
- JVM会保证一个类的
<clinit>()
方法在多线程环境中被正确地加锁和同步。
- 执行类构造器
类加载器的重要特性
类加载器的重要特性包括:
- 完全性(Full delegation):在尝试加载类时,除非父类加载器明确拒绝(返回 null),否则总是首先请求父类加载器加载。
- 无干扰性(Non-interference):类加载器不会干预已加载类的状态和数据。
- 缓存机制:一旦类被加载到JVM中,它会被缓存,后续的加载请求将返回相同的
Class
实例。
类加载器的实现
实现自定义类加载器通常涉及重写ClassLoader
类的findClass
方法。下面是一个简单的自定义类加载器的实现示例:
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// 加载类文件的字节码
// ...
}
}
类加载器的使用场景
类加载器在Java编程中有多种使用场景,其中包括:
- 模块化:使用不同的类加载器加载不同模块,以保持模块独立性。
- 热部署/热替换:在服务器运行时动态加载和替换类,不需要重启服务器。
- 插件系统:允许第三方插件在运行时被添加到应用程序中。
- 隔离:防止代码库之间的冲突,通过使用不同的类加载器来加载它们。
理解并了解类加载器的工作原理是Java开发者提高自己技能的重要一步,尤其是在处理复杂的应用程序和解决类加载相关问题时。