Java 类加载器介绍
Java 类加载器(Class Loader)是 Java 虚拟机(JVM)的一个核心子系统,它的主要职责是在运行时根据类的全限定名(例如 java.lang.String)来定位并加载对应的 .class 文件的二进制字节流,然后将这些字节流解析成 JVM 内部的数据结构,并在堆中创建一个 java.lang.Class 对象。
Java 系统中主要包含以下三种类加载器,它们共同构成了一个具有层次关系的结构。
-
启动类加载器 (Bootstrap ClassLoader)
-
这是最顶层的加载器,是虚拟机自身的一部分。
-
它负责加载 Java 的核心库,即
<JAVA_HOME>/jre/lib目录下的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库(如rt.jar)。 -
出于安全考虑,Java 程序无法直接获取到启动类加载器的引用,所以当你尝试获取它的父加载器时,会返回
null。
-
-
扩展类加载器 (Extension ClassLoader)
-
它负责加载
<JAVA_HOME>/jre/lib/ext目录中,或者被java.ext.dirs系统变量所指定的路径中的所有类库。 -
开发者可以直接使用扩展类加载器。
-
-
应用程序类加载器 (Application ClassLoader)
-
也称为系统类加载器 (System ClassLoader)。它负责加载用户类路径(Classpath)上所指定的类库。
-
它是程序中默认的类加载器。我们自己编写的 Java 类,在没有自定义类加载器的情况下,通常都是由它来加载的。
-
这三者之间的关系如下图所示(注意:这种父子关系是通过组合来实现的,而不是继承):
null
^
| (父加载器)
+-------------------------+
| Bootstrap ClassLoader |
+-------------------------+
^
| (父加载器)
+-------------------------+
| Extension ClassLoader |
+-------------------------+
^
| (父加载器)
+-------------------------+
| Application ClassLoader |
+-------------------------+
自定义类加载器 (Custom Class Loader)
Java 默认的类加载器(启动、扩展、应用)只能从本地文件系统的 Classpath 中加载类。但在很多复杂的应用场景中,这远远不够。自定义类加载器的核心作用就是 扩展类的加载来源,并实现特定的加载逻辑。
它的主要用途(作用)可以归结为以下几点:
-
实现类的隔离:这是最重要的用途之一。在大型应用或服务器中(如 Tomcat、OSGi 框架),可能需要同时运行多个模块或应用,而这些应用可能依赖于同一个库的不同版本。通过为每个应用创建独立的类加载器,可以加载各自版本的库,互不干扰,避免了“类冲突”。
-
从非标准来源加载类:默认加载器只能从文件系统加载。自定义加载器可以从任何地方加载类的字节码,例如:
-
网络:从远程服务器下载
.class文件或 JAR 包来执行,实现动态分发代码。 -
数据库:将类的字节码存储在数据库中,运行时按需加载。
-
内存:动态生成类的字节码(例如使用 ASM、Javassist 等字节码操作库),然后直接从内存中加载这个新生成的类。
-
-
实现代码热部署/热替换:在不停止服务的情况下,更新应用代码。通过创建一个新的类加载器实例来加载新版本的类文件,并替换掉旧的加载器,就可以实现类的更新。旧的类加载器及其加载的旧版本类对象,在没有引用后会被垃圾回收。
-
加载加密的字节码:为了保护代码不被反编译,可以对编译后的
.class文件进行加密。然后通过自定义类加载器,在加载字节码时先进行解密,再交给 JVM。标准类加载器无法识别加密过的文件。
具体的应用场景非常广泛:
-
Web服务器和应用服务器:最典型的例子是 Tomcat。每个部署在 Tomcat 上的 Web 应用(WAR包)都有一个自己的
WebAppClassLoader。这样,应用 A 可以使用log4j-1.2.jar,而应用 B 可以使用log4j-2.0.jar,它们不会冲突。 -
插件化框架:比如 Eclipse 的插件体系、OSGi 框架。每个插件(Bundle)都由自己的类加载器加载,实现了高度的模块化和隔离,插件可以独立安装、启动、停止和卸载。
-
热部署工具:像 JRebel 或 Spring Boot DevTools 等工具,它们通过监控文件变化,并使用自定义类加载器重新加载修改过的类,从而实现代码的即时生效,极大地提升了开发效率。
-
代码保护和授权:在一些商业软件中,核心模块的
.class文件被加密,软件运行时通过一个自定义的类加载器进行解密和验证授权,然后才加载到内存中。 -
规则引擎和脚本执行:一些系统允许用户动态编写业务逻辑脚本(如 Groovy、Drools 规则),系统会将这些脚本动态编译成字节码,然后用自定义类加载器加载并执行。