JVM类加载器以及双亲委派模型
类加载生命周期
类从被加载到Java虚拟机(JVM)内存开始,直至从内存中卸载,其完整的生命周期可分为七个阶段:
-
加载(Loading):查找并导入二进制字节流(.class文件),创建类的Class对象。
-
验证(Verification):确保被加载类的正确性,包括格式校验、语义校验、操作数栈和局部变量表的验证等。
-
准备(Preparation):为类的静态变量分配内存,并初始化为默认值(零值),但不执行任何实际的初始化赋值操作。
-
解析(Resolution):将符号引用转换为直接引用,如将类方法表中的方法引用解析为实际的方法地址。
-
初始化(Initialization):执行类构造器
()方法,进行静态变量初始化和静态初始化块的执行。 -
使用(Using):类被JVM使用,执行其定义的方法和字段操作。
-
卸载(Unloading):当类不再被引用时,由垃圾收集器触发,卸载类的Class对象,释放其占用的内存资源。
其中,验证、准备和解析这三个阶段共同构成了连接(Linking)阶段。
在Java虚拟机(JVM)中,类加载器(ClassLoader)是负责加载类的组件,它负责从文件系统或网络中查找类的二进制数据,并转换成可在Java虚拟机内部使用的运行时数据结构(Class对象)。类加载器是Java平台实现动态类加载的关键组成部分,也是实现Java语言模块化和隔离的重要工具。
类加载器分类:
-
引导类加载器(Bootstrap ClassLoader):
- 这是最顶层的类加载器,由C++编写,嵌入在JVM内核中,负责加载核心类库,如
rt.jar
等。 - 由于引导类加载器是由C++实现的,所以在Java程序中通常显示为
null
,无法直接通过java.lang.ClassLoader
的子类实例获取。
- 这是最顶层的类加载器,由C++编写,嵌入在JVM内核中,负责加载核心类库,如
-
扩展类加载器(Extension ClassLoader):
- 是由Java实现的,位于引导类加载器之下,负责加载
<JAVA_HOME>/lib/ext
目录下的类库,或者由java.ext.dirs
系统变量指定的目录下的类库。
- 是由Java实现的,位于引导类加载器之下,负责加载
-
系统类加载器(Application ClassLoader):
- 也称作应用程序类加载器,是Java程序默认的类加载器,负责加载classpath环境变量所指定的类库,通常是我们应用程序中的类加载入口。
-
自定义类加载器(Custom ClassLoader):
- 开发者可以基于
java.lang.ClassLoader
类自定义类加载器,用于实现特定的类加载逻辑,例如从网络、数据库或者其他非传统途径加载类。
- 开发者可以基于
双亲委派模型:
- 类加载器在加载类时,遵循“双亲委派模型”。当一个类加载器收到类加载请求时,它首先将请求委派给父类加载器去加载,直到到达引导类加载器。只有当父加载器无法完成类加载请求(返回
null
)时,当前加载器才会尝试自己去加载类。 - 这种模型确保了类加载的有序性、稳定性以及安全性,特别是确保了Java基础类库的统一加载来源。
例如,当我们创建一个自定义类MyClass
并继承自java.lang.Object
时,类加载器会首先由系统类加载器尝试加载MyClass
,然后沿着双亲委派链向上,如果需要的话,会加载Object
类。即使MyClass
在自定义类加载器中,基础类Object
也会由引导类加载器加载。
双亲委派机制(父类委托机制):
在双亲委派模型下,当一个类加载器收到类加载请求时,它首先会将请求转发给其父类加载器。这一过程递归向上,直到请求传递到启动类加载器。只有当父加载器无法完成类加载请求(即在其搜索范围内找不到所需类)时,当前加载器才会尝试自行加载。这个过程确保了Java核心类库的唯一性和安全性。
具体实现在java.lang.ClassLoader
的loadClass()
方法中,加载过程首先检查类是否已加载,未加载则调用父加载器的loadClass()
方法,若父加载器为空,则默认使用启动类加载器作为父加载器。如果父加载器加载失败,抛出ClassNotFoundException
异常后,子类加载器再调用自己的findClass()
方法尝试加载类。例如:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 若父加载器为空,则默认使用启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载器加载失败,尝试自己加载
}
if (c == null) {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 自定义的类加载逻辑...
}
}
例如,如果我们定义一个类Class A extends HttpServlet
,在加载A
类时,类加载器会按照双亲委派模型,先尝试由其父加载器加载HttpServlet
及其依赖类,只有当父加载器无法完成加载时,才会由当前加载器尝试加载。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!