在使用Java 语言开发时,Class Loader可能是一个不用关注的概念,但是在某些疑难问题的解决的时候,可能需要掌握相关的知识,比如笔者在这一篇遇到的问题: Spring Boot JPA 开发之Not an entity血案。 接下来就来全面的看一看Java的类加载机制。
一、类加载器(Class Loader)
类加载器是JVM的一部分,负责将类的字节码文件(.class
)动态加载到内存中,并生成对应的java.lang.Class
对象。Java采用分层类加载模型,主要有以下三类加载器:
- Bootstrap ClassLoader(启动类加载器):
- 职责:加载JVM核心类库(如
java.lang.*
),路径为<JAVA_HOME>/jre/lib
。 - 实现:由C/C++编写,是JVM的一部分,无Java类实例。
- 特性:唯一没有父加载器的类加载器。
- 职责:加载JVM核心类库(如
- Extension ClassLoader(扩展类加载器):
- 职责:加载扩展目录(
<JAVA_HOME>/jre/lib/ext
)中的类。 - 实现:由
sun.misc.Launcher$ExtClassLoader
实现,父加载器为Bootstrap。
- 职责:加载扩展目录(
- Application ClassLoader(应用程序类加载器):
- 职责:加载用户类路径(
-classpath
或CLASSPATH
环境变量)的类。 - 实现:由
sun.misc.Launcher$AppClassLoader
实现,父加载器为Extension。
- 职责:加载用户类路径(
- 自定义类加载器:
- 场景:需隔离加载(如热部署)、动态加载网络资源等。
- 实现:继承
ClassLoader
,重写findClass()
方法。
二、类加载机制
类加载过程分为加载、链接(验证、准备、解析)、初始化三个阶段:
- 加载(Loading):
- 操作:查找字节码文件,读取到内存,生成
Class
对象。 - 触发条件:首次使用类时(如
new
、调用静态方法等)。
- 操作:查找字节码文件,读取到内存,生成
- 链接(Linking):
- 验证(Verification):检查字节码合法性(格式、语义、安全性)。
- 准备(Preparation):为静态变量分配内存并赋默认值(如
int
初始化为0)。 - 解析(Resolution):将符号引用(类、方法名)转为直接引用(内存地址)。
- 初始化(Initialization):
- 操作:执行静态变量赋值和静态代码块(按代码顺序执行)。
- 线程安全:JVM保证初始化过程同步。
三、双亲委派模型(Parent Delegation Model)
- 机制:
- 类加载器在加载类时,先将请求委派给父加载器。
- 若父加载器无法完成(在自己的搜索范围内找不到类),子加载器才尝试加载。
- 优点:
- 安全性:防止核心类被篡改(如用户自定义
java.lang.String
无效)。 - 避免重复:确保类全局唯一性(同一类由同一加载器加载)。
- 安全性:防止核心类被篡改(如用户自定义
- 打破双亲委派:
- 场景:OSGi模块化、Tomcat隔离Web应用。
- 方法:重写
loadClass()
逻辑(如优先自行加载)。
四、类对象(Class Object)
- 概念:
- 每个类加载后,JVM为其生成一个
Class
对象,存储类的元数据(方法、字段、构造器等)。 - 是反射(
java.lang.reflect
)操作的基础。
- 每个类加载后,JVM为其生成一个
- 获取方式:
类名.class
(如String.class
)。对象.getClass()
(如new String().getClass()
)。Class.forName("全限定类名")
(动态加载,如Class.forName("java.util.ArrayList")
)。
- 应用:
- 反射:通过
Class
对象实例化对象、调用方法、访问字段。 - 动态代理:基于接口和
Class
对象生成代理类。
- 反射:通过
五、关键代码示例
// 自定义类加载器
public class CustomClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = loadClassData(name); // 从自定义路径读取字节码return defineClass(name, classData, 0, classData.length);}
}// 使用Class对象反射创建实例
Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.newInstance();
Method method = clazz.getMethod("myMethod");
method.invoke(obj);
六、常见问题
- 不同类加载器加载的类是否相同?
- 否:即使全限定名相同,不同加载器加载的类在JVM中视为不同类,导致
instanceof
和类型转换失败。
- 否:即使全限定名相同,不同加载器加载的类在JVM中视为不同类,导致
- 何时触发类初始化?
new
、静态方法调用、静态字段访问(非final)、反射调用Class.forName()
(默认初始化)、子类初始化触发父类初始化等。
- 如何实现热部署?
- 自定义类加载器,每次重新加载类生成新
Class
对象,需注意旧实例与新类不兼容。
- 自定义类加载器,每次重新加载类生成新