Java 类加载器
类加载器遵循 双亲委派模型(JDK 9+ 弱化但仍保留核心逻辑):请求加载类时,先委托父加载器加载,父加载器无法加载时才自己加载(核心目的是避免类重复加载、保证核心类安全)。
JDK 8 及更早版本
分为 4 种类加载器,加载路径与 JVM 核心目录强关联
Bootstrap ClassLoader
不是 Java 语言实现,而是由 C/C++ 编写的 JVM 内核组件,属于 JVM 自身的一部分。无对应的 java.lang.ClassLoader 子类。用于加载 JVM 核心类库(保证 Java 基础运行环境)。
加载路径:
核心路径:$JAVA_HOME/jre/lib 目录下的核心 jar 包(如 rt.jar、charsets.jar、sunrsasign.jar 等),且仅加载符合 JVM 识别的 jar(由 Xbootclasspath 参数限定)。
无父加载器,加载的类属于「启动类加载器命名空间」,ClassLoader.getParent() 返回 null(因为不是 Java 对象)。
可通过System.getProperty("sun.boot.class.path") 查看其加载路径。
// Linux上分隔符为:
for (String classpath : System.getProperty("sun.boot.class.path").split(";")) {
System.out.println(classpath);
}
// 结果如下:
$JAVA_HOME\jre\lib\resources.jar
$JAVA_HOME\jre\lib\rt.jar
$JAVA_HOME\jre\lib\jsse.jar
$JAVA_HOME\jre\lib\jce.jar
$JAVA_HOME\jre\lib\charsets.jar
$JAVA_HOME\jre\lib\jfr.jar
$JAVA_HOME\jre\classes
上面$JAVA_HOME\jre\classes这个目录实际上在JDK安装后默认是不存在的,
扩展类加载器 Extension ClassLoader
扩展类加载器,具体的类为 sun.misc.Launcher$ExtClassLoader,由Java 实现,继承自 URLClassLoader,父加载器是启动类加载器。职责是加载 JVM 扩展功能的类库。JDK 8 中已逐步被模块化替代,JDK 9+ 完全移除。
加载目录 / 路径:
- 核心路径:
$JAVA_HOME/jre/lib/ext目录下的所有 jar 包。 - 扩展路径:系统属性
java.ext.dirs指定的目录(可通过-Djava.ext.dirs自定义)。
系统属性 java.ext.dirs
可通过System.getProperty("java.ext.dirs") 查看其加载路径。
for (String dir : System.getProperty("java.ext.dirs").split(";")) {
System.out.println(dir);
}
// 结果如下
%JAVA_HOME%\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
系统类加载器
应用程序类加载器(Application ClassLoader)/ 系统类加载器(System ClassLoader)
具体的类名是 sun.misc.Launcher$AppClassLoader。由 Java 实现,继承自 URLClassLoader,父加载器是扩展类加载器。职责是加载应用程序自身的类(项目自身的代码以及第三方依赖)。
加载目录 / 路径:系统属性 java.class.path指定的路径,包括:
- 环境变量 CLASSPATH 指定的路径;
- 项目编译后的 target/classes(或 bin)目录;
- 第三方依赖 jar 包(如 lib/*.jar);
- 启动参数
-cp/-classpath指定的路径(优先级最高)。
特点:默认的类加载器(如 Class.forName() 未指定加载器时,默认使用此类加载器)。
验证方式:System.getProperty("java.class.path") 查看加载路径。
java.system.class.loader
系统属性java.system.class.loader可用于自定义系统类加载器
ClassLoader#getParent
ClassLoader#getParent 方法返回委派的父类加载器。一些JVM实现会使用 null 来表示引导类加载器,比如Oracle的Hotspot。如果该类加载器的父级是引导类加载器,则在此类实现中,ClassLoader#getParent()方法将返回null。
public class Main {
public static void main(String[] args) {
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(appClassLoader); // sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println(extClassLoader); // sun.misc.Launcher$ExtClassLoader@1b6d3586
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader); // null
}
}
自定义类加载器
本质:开发者继承 java.lang.ClassLoader 实现的自定义加载器(需重写 findClass() 方法,JDK 8 前若重写 loadClass() 会破坏双亲委派)。
核心职责:加载非标准路径的类(如网络、加密文件、自定义目录)。
加载目录:完全自定义(如 /opt/myclasses/、网络 URL、内存字节流等)。
典型场景:Tomcat 类加载器(隔离不同 Web 应用)、热部署、加密类加载。
Java 9及以后
JDK 9 引入 模块系统,废弃 rt.jar、ext 目录,类加载器体系重构,核心保留 3 类(启动类加载器逻辑调整,扩展类加载器被移除)。
启动类加载器(Bootstrap ClassLoader)
变化:不再加载 rt.jar,而是加载 JDK 核心模块(如 java.base、java.lang 等),模块路径为$JAVA_HOME/jmods/
部分功能委托给「平台类加载器」,且首次支持通过 Java 代码访问(ClassLoader.getPlatformClassLoader())。
加载路径:
核心模块:$JAVA_HOME/jmods/ 下的系统模块(由 --module-path 或 --add-modules 指定)。
系统属性:jdk.boot.class.path.append 可追加加载路径(替代原 sun.boot.class.path)。
特点:仍为 C/C++ 实现,但命名空间与平台类加载器整合。
平台类加载器(Platform ClassLoader)
平台类加载器,替代原扩展类加载器。
Java 实现(jdk.internal.loader.PlatformClassLoader),父加载器是启动类加载器。
核心职责:加载 JDK 平台模块(非核心但扩展的模块,如 java.sql、java.xml)和非模块的兼容类。
加载路径:
模块路径:$JAVA_HOME/lib/modules(JDK 内置平台模块)。
系统属性:java.platform.class.path 可指定加载路径(替代原 java.ext.dirs)。
启动参数:--platform-module-path 指定平台模块路径。
特点:
遵循双亲委派,但允许「向下委派」,平台类加载器可委托应用类加载器,打破严格双亲委派)。
默认可见性:仅对系统模块和 java.ext.dirs 兼容路径可见。
应用程序类加载器(Application ClassLoader)
别名:系统类加载器(System ClassLoader),类名改为 jdk.internal.loader.AppClassLoader。
核心职责:加载应用模块(module-info.java 定义的应用模块)和类路径(ClassPath)下的非模块类。
加载路径:
模块路径:--module-path(-p)指定的应用模块路径(优先加载)。
类路径:java.class.path(ClassPath)仍有效(兼容旧代码),但优先级低于模块路径。
启动参数:--class-path(-cp)仍支持,用于加载非模块化的类。
特点:
父加载器是平台类加载器。
模块化场景下,优先加载 module-path 中的模块类,再加载 class-path 中的类。
自定义类加载器(Custom ClassLoader)
兼容:仍支持继承 ClassLoader 实现,但推荐遵循模块化规则(如通过 ModuleLayer 加载模块)。
变化:
重写 loadClass() 不再强制破坏双亲委派,JDK 9+ 允许「并行加载」和「模块层隔离」。
支持加载模块化类(需通过 ModuleFinder 和 Configuration 构建模块层)。
加载路径:仍自定义,但可整合模块路径(ModulePath)和类路径(ClassPath)。
如何知道类加载器加载了哪些路径
对于所有继承自URLClassLoader的类加载器,内部持有一个URLClassPath实例
public class URLClassLoader extends SecureClassLoader implements Closeable {
/* The search path for classes and resources */
private final URLClassPath ucp;
}
这个属性是私有的,可以通过反射强行访问,另一种方式是通过jdk 内部提供的 sun.misc.SharedSecrets 这个类,它暴露出了一些内部API,可以像下面这样拿到 URLClassPath 实例:
URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
URLClassPath urlClassPath = SharedSecrets.getJavaNetAccess().getURLClassPath(appClassLoader);
for (URL url : urlClassPath.getURLs()) {
System.out.println(url);
}
-verbose:class
-verbose:class 是 Java 启动参数(JVM 参数),用于详细输出类加载 / 卸载的全过程,包括类的加载来源(JAR 包路径)、加载顺序、卸载时机等。核心作用:排查类加载相关问题(如类冲突、类找不到、重复加载、类版本不一致等)。
# 执行普通 Java 类(含 main 方法)
java -verbose:class YourMainClass
# 执行 JAR 包
java -verbose:class -jar YourApp.jar
# 结合其他参数(如指定类路径)
java -verbose:class -cp ./lib/*:./classes YourMainClass
与其他参数结合使用
# 仅输出类卸载(Java 8+)
java -verbose:class -XX:+TraceClassUnloading YourMainClass
# 输出类加载 / 卸载的详细统计(Java 8+), 程序退出时会输出类实例数量、内存占用等统计信息。
java -verbose:class -XX:+PrintClassHistogram YourMainClass
# 结合类加载器日志(定位类加载器问题)
# -XX:+TraceClassLoading:等价于 -verbose:class(增强版);
# -XX:+TraceClassLoadingPreorder:输出类加载的前置依赖顺序。
java -verbose:class -XX:+TraceClassLoading -XX:+TraceClassLoadingPreorder YourMainClass
总结
| 特性 | JDK 8 | JDK 9+ |
|---|---|---|
| 核心类库形式 | rt.jar、ext/*.jar | 模块化 jmods/ |
| 扩展类加载器 | 存在(ExtClassLoader) | 移除(替换为平台类加载器) |
| 类路径优先级 | ClassPath 唯一 | ModulePath > ClassPath |
| 系统属性 | java.ext.dirs | java.platform.class.path |
| 启动参数 | -Djava.ext.dirs | --platform-module-path |
关键版本差异总结
| JDK 版本 | 核心类加载器 | 核心加载目录 / 形式 | 核心变化 |
|---|---|---|---|
| JDK 8- | 启动 + 扩展 + 应用 + 自定义 | jre/lib/rt.jar、jre/lib/ext、ClassPath | 经典双亲委派,基于 jar 加载 |
| JDK 9+ | 启动 + 平台 + 应用 + 自定义 | jmods/、ModulePath、ClassPath | 模块化加载,平台类加载器替代扩展类,弱化严格双亲委派 |

浙公网安备 33010602011771号