类加载器(ClassLoader)是Java虚拟机(JVM)用来动态加载Java类到内存中的组件。它们在Java运行时环境中扮演着非常重要的角色,主要负责找到和加载类的字节码,并将它们转化为内存中的类对象。下面详细介绍类加载器的工作机制:
1. 类加载器的种类
三种类加载器
- **启动类加载器(Bootstrap ClassLoader)**:用来加载Java核心库(例如`rt.jar`)。
- **扩展类加载器(Extension ClassLoader)**:用来加载Java扩展库(位于`jre/lib/ext`目录下的类)。
- **应用程序类加载器(Application ClassLoader)**:用来加载用户类路径(Classpath)上指定的类。
2. 类加载器的行为
1.1 加载(Loading)
加载阶段由类加载器负责,它包括以下步骤:
- 通过类的全限定名获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的`java.lang.Class`对象。
1.2 连接(Linking)
连接阶段又分为验证、准备和解析三个步骤:
- 验证(Verification):确保被加载的类的字节流符合JVM规范,没有安全问题。
- 准备(Preparation):为类的静态变量分配内存,并将其初始化为默认值。
- 解析(Resolution):将类、接口、方法和字段的符号引用替换为直接引用。
1.3 初始化(Initialization)
初始化阶段主要是执行类构造器<clinit>()方法,包括:
- 对静态变量赋初始值
- 执行静态代码块
3. 类加载器的工作模型
双亲委派模型
它的工作流程如下:
1. 类加载请求从子类加载器开始:
当某个类加载器(比如自定义类加载器)接收到一个类加载请求时,它不会立即尝试加载这个类。
2. 向上委派到父类加载器:
子类加载器会将这个请求委派给它的父类加载器,依次向上,直到到达顶层的启动类加载器(Bootstrap ClassLoader)。
3. 父类加载器尝试加载类:
每一个类加载器在接收到加载请求时,会先检查自己是否已经加载过这个类(类缓存),如果没有加载过,再将请求向它的父类加载器委派。
4. 递归委派直到启动类加载器:
这个委派过程是递归进行的,一直递归到启动类加载器为止。启动类加载器是最顶层的类加载器,负责加载Java核心类库(如`java.lang.String`等)。
5. 启动类加载器尝试加载类:
如果启动类加载器找到了请求的类(通常是Java核心库中的类),就会加载并返回该类。
6. 如果父类加载器未能加载类:
如果启动类加载器和其他父类加载器都未能加载该类,那么子类加载器才会尝试自己加载这个类。
这种机制确保了Java核心类库的安全性和一致性,防止了用户自定义类覆盖Java核心类库中的类,同时确保同一个类不会被重复加载,保证同一个类在不同类加载器环境中都是同一个实例。
4. 自定义类加载器
Java允许开发者创建自定义类加载器,以实现特殊需求。例如,可以通过继承`java.lang.ClassLoader`类并重写`findClass`方法来实现。
以下是一个简单的自定义类加载器示例:
总结来说,类加载器在Java中具有重要的作用,它通过加载、连接和初始化来将类加载到内存中,并通过双亲委派模型来保证类加载的安全性和一致性。同时,Java还允许开发者创建自定义类加载器,以满足特定的需求。