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+ 完全移除。

加载目录 / 路径:

  1. 核心路径:$JAVA_HOME/jre/lib/ext 目录下的所有 jar 包。
  2. 扩展路径:系统属性 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指定的路径,包括:

  1. 环境变量 CLASSPATH 指定的路径;
  2. 项目编译后的 target/classes(或 bin)目录;
  3. 第三方依赖 jar 包(如 lib/*.jar);
  4. 启动参数 -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 模块化加载,平台类加载器替代扩展类,弱化严格双亲委派
posted @ 2025-12-24 20:53  vonlinee  阅读(13)  评论(0)    收藏  举报