Java 中的 ClassLoader

17 Mar 2025 | 5 分钟阅读

Java ClassLoader

Java ClassLoader 是一个抽象类。它属于 java.lang 包。它从不同的资源加载类。Java ClassLoader 用于在运行时加载类。换句话说,JVM 在运行时执行链接过程。类根据需要加载到 JVM 中。如果一个已加载的类依赖于另一个类,那么该类也会被加载。当我们请求加载一个类时,它会将该类委托给其父类加载器。这样,在运行时环境中就维护了唯一性。这对于执行 Java 程序至关重要。

ClassLoader in Java

Java ClassLoader 基于三个原则:委派(Delegation)可见性(Visibility)唯一性(Uniqueness)

  • 委派原则: 它将类加载的请求转发给父类加载器。只有当父类加载器找不到或无法加载该类时,它才会加载该类。
  • 可见性原则: 它允许子类加载器看到父类加载器加载的所有类。但父类加载器看不到子类加载器加载的类。
  • 唯一性原则: 它只允许一个类被加载一次。这是通过委派原则实现的。它确保子类加载器不会重新加载父类加载器已经加载过的类。

ClassLoader 的类型

在 Java 中,每个 ClassLoader 都有一个预定义的加载类文件的位置。Java 中有以下几种 ClassLoader 类型:

Bootstrap Class Loader(启动类加载器): 它从 rt.jar 加载标准的 JDK 类文件和其他核心类。它是所有类加载器的父类。它没有父类。当我们调用 String.class.getClassLoader() 时,它返回 null,任何基于它的代码都会抛出 NullPointerException。它也称为 Primordial ClassLoader。它从 jre/lib/rt.jar 加载类文件。例如,java.lang 包中的类。

Extensions Class Loader(扩展类加载器): 它将类加载请求委托给其父类加载器。如果类加载不成功,它会从 jre/lib/ext 目录或 java.ext.dirs 指定的任何其他目录加载类。它在 JVM 中由 sun.misc.Launcher$ExtClassLoader 实现。

System Class Loader(系统类加载器): 它从 CLASSPATH 环境变量加载应用程序特定的类。它可以在调用程序时使用 -cp 或 classpath 命令行选项进行设置。它是 Extension ClassLoader 的子类。它由 sun.misc.Launcher$AppClassLoader 类实现。所有 Java ClassLoader 都实现了 java.lang.ClassLoader。

ClassLoader in Java

Java ClassLoader 的工作原理

当 JVM 请求加载一个类时,它会调用 java.lang.ClassLoader 类的 loadClass() 方法,并传入类的完全限定名。loadClass() 方法会调用 findLoadedClass() 方法来检查该类是否已经被加载。这是为了避免多次加载同一个类。

如果类已经被加载,它会将请求委托给父 ClassLoader 来加载。如果 ClassLoader 找不到该类,它会调用 findClass() 方法在文件系统中查找类。下图展示了 ClassLoader 在 Java 中如何使用委派机制加载类。

ClassLoader in Java

假设我们有一个应用程序特定的类 Demo.class。加载这个类文件的请求会传递给 Application ClassLoader。它会将请求委托给它的父类 Extension ClassLoader。然后,它会委托给 Bootstrap ClassLoader。Bootstrap ClassLoader 在 rt.jar 中搜索该类,但发现它不在那里。现在,请求会转给 Extension ClassLoader,它会在 jre/lib/ext 目录中搜索,并尝试在那里找到该类。如果类在那里找到,Extension ClassLoader 就会加载该类。Application ClassLoader 根本不会加载该类。当 Extension ClassLoader 无法加载该类时,Application ClassLoader 才会从 Java 的 CLASSPATH 中加载它。

可见性原则规定,子类加载器可以看到父类加载器加载的类,但反之则不然。这意味着,如果 Application ClassLoader 加载了 Demo.class,那么尝试使用 Extension ClassLoader 显式加载 Demo.class 就会抛出 java.lang.ClassNotFoundException。

根据唯一性原则,父类加载器加载的类不应该被子类加载器再次加载。因此,可以编写一个违背委派和唯一性原则的类加载器,并自行加载类。

总之,类加载器遵循以下规则:

  • 它检查类是否已加载。
  • 如果类未加载,则请求父类加载器加载该类。
  • 如果父类加载器无法加载该类,则尝试在此类加载器中加载。

考虑以下示例

使用以下命令编译并运行上述代码:

-verbose:class: 它用于显示 JVM 加载的类的相关信息。当动态加载类时,它很有用。下图显示了输出。

ClassLoader in Java

我们可以观察到,应用程序类(Demo)所需的运行时类首先被加载。

何时加载类

只有两种情况:

  • 当执行新的字节码时。
  • 当字节码对类进行静态引用时。例如,System.out

静态类加载与动态类加载

类是使用 "new" 运算符进行静态加载的。动态类加载是通过使用 Class.forName() 方法在运行时调用类加载器的函数来实现的。

loadClass() 和 Class.forName() 之间的区别

loadClass() 方法仅加载类,但不初始化对象。而 Class.forName() 方法在加载类之后初始化对象。例如,如果您使用 ClassLoader.loadClass() 来加载 JDBC 驱动程序,类加载器不允许加载 JDBC 驱动程序。

java.lang.Class.forName() 方法使用给定的字符串名称返回与该类或接口关联的 Class 对象。如果找不到类,它会抛出 ClassNotFoundException。

示例

在这个例子中,加载了 java.lang.String 类。它打印类名、包名以及 String 类所有可用方法的名称。我们在以下示例中使用 Class.forName()。

Class<?>: 表示一个 Class 对象,它可以是任何类型(? 是一个通配符)。Class 类型包含关于类的元信息。例如,String.class 的类型是 Class<String>。如果被建模的类未知,则使用 Class<?>。

getDeclaredMethod(): 返回一个 Method 对象数组,这些对象反映了此 Class 对象表示的类或接口的所有声明的方法,包括公共、受保护、默认(包)访问和私有方法,但不包括继承的方法。

getName(): 它以 String 的形式返回此 Method 对象所表示的方法名称。

输出

Class Name: java.lang.String
Package Name: package java.lang
-----Methods of String class -------------
value
coder
equals
length
toString
hashCode
getChars
------
------
------
intern
isLatin1
checkOffset
checkBoundsOffCount
checkBoundsBeginEnd
access$100
access$200

下一个主题Java 教程