类加载器
类加载器
一、类加载器是什么
**类加载器(ClassLoader)**负责把 .class 文件加载到 JVM 内存中,并生成对应的 Class 对象。
本质:
- 输入:字节码(
.class文件) - 输出:
Class<?>对象(方法区/元空间中)
二、类加载过程
类加载器主要参与的是:
1 | 加载(Loading) → 验证 → 准备 → 解析 → 初始化 |
类加载器只负责第一步:加载(Loading)
具体做:
- 根据类的全限定名找到字节码
- 读取
.class - 转换为 JVM 内部结构
- 生成
Class对象
三、类加载器的分类
Java 中有三种核心类加载器:
3.1 启动类加载器(Bootstrap ClassLoader)
-
用 C++ 实现(不是 Java 类)
-
负责加载:
1
<JAVA_HOME>/lib
-
比如:
rt.jar(JDK8)- 核心类:
String、Object
特点:
- 最顶层加载器
- 没有父加载器
3.2 扩展类加载器(Extension ClassLoader)
-
Java 实现
-
加载目录:
1
<JAVA_HOME>/lib/ext
3.3 应用类加载器(Application ClassLoader)
-
又叫 系统类加载器
-
负责加载:
1
classpath
-
也就是你写的代码
面试重点:
默认情况下,我们写的类都是它加载的
3.4 自定义类加载器(User-defined)
你可以继承 ClassLoader 自己实现:
1 | class MyClassLoader extends ClassLoader { |
使用场景:
- 热部署(Tomcat)
- 加密 class
- 动态加载
四、双亲委派模型 ⭐(重点)
4.1 什么是双亲委派?
当一个类加载请求来时:
1 | 当前加载器 → 先问父加载器 → 一直往上 → Bootstrap |
加载顺序:
1 | Application |
4.2 工作流程
假设要加载 java.lang.String:
- Application 收到请求
- 交给 Extension
- Extension 交给 Bootstrap
- Bootstrap 找到了 → 加载成功
- 返回结果
4.3 为什么要这样设计?
- 防止核心类被篡改
你写一个假的:
1 | package java.lang; |
不会生效,因为:
- Bootstrap 已经加载了真正的
String
- 避免重复加载
同一个类只会被加载一次
4.4 注意点(面试高频)
双亲委派不是强制的
- 可以被打破(如 Tomcat)
五、如何打破双亲委派
典型场景:
5.1 重写 loadClass 方法
- 类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器
loadClass()方法来加载类)。 - 重写
loadClass()方法之后,我们就可以改变传统双亲委派模型的执行流程。
5.1 Tomcat
- 每个 Web 应用有独立 ClassLoader
- 可以加载不同版本的类
为什么要打破?
- 不同应用可能依赖不同版本的库
5.2 SPI(Service Provider Interface)
比如:
- JDBC
ServiceLoader
原因:
- 核心类需要加载第三方实现
解决:
- 线程上下文类加载器(Thread Context ClassLoader)
六、类加载器的核心方法
1 | loadClass() |
流程:
1 | 1. 检查是否已加载 |
七、面试高频总结
⭐ Q1:类加载器有哪些?
- Bootstrap(启动类加载器)
- Extension(扩展类加载器)
- Application(应用程序类加载器)
- 自定义
⭐ Q2:双亲委派模型作用?
- 防止篡改核心类
- 避免重复加载
⭐ Q3:什么时候会破坏双亲委派?
- 重写
loadClass方法 - Tomcat
- SPI(JDBC)
⭐ Q4:类加载器负责什么?
只负责:
- 加载字节码 → 生成
Class对象
八、一句话总结
类加载器负责把
.class加载进 JVM,而双亲委派模型保证了加载的安全性和唯一性。