Learn · concepts

什么是 Java 类加载器??

类加载器在运行时在JVM中查找并加载Java类。 它们的工作方式、层级结构的重要性以及模组为何依赖于它们,请参见下文.

TRtrolPublished 1 min read

类加载器是 Java 运行时环境的一部分,负责查找类文件并将它们加载到 JVM 中。 每次你的代码首次访问一个类型时,JVM 都会询问一个类加载器:“帮我找到这个类。” 加载器会定位字节码,读取它,然后 JVM 在内存中定义该类。 这种机制在正常的 Java 程序中不可见,但它是模组框架用来修补 Minecraft 的确切钩子。

类加载器实际上做什么

类加载器将一个类名转换为运行中的 JVM 内部可用的类。 你请求一个类型,加载器会找到匹配的 .class 文件(或生成它),验证并链接它,然后该类就可以使用了。

工作分为三个步骤:

  1. 查找 — 加载器定位名为类的字节码(来自 JAR 文件、磁盘、网络或动态生成)。
  2. 验证和链接 — JVM 检查字节码是否有效,并解析对其他类的引用。
  3. 使用 — 类已准备就绪,你的程序可以实例化它。

这就是为什么 Java 程序可以在运行时动态加载在编译时未提及的代码。 类名就足以让加载器去找到它。

类加载器层次结构

类加载器以父子链的形式排列,其中大多数加载器首先询问其父加载器。 一个标准的 Java 应用程序有三个内置的类加载器:

加载器加载内容目的
启动加载器核心 Java 运行时类确保核心类型(如 java.lang.Object)始终相同
平台加载器核心之外的标准库模块Java 模块和扩展
应用加载器您程序自己的类您程序指定的类路径

当应用加载器被请求加载一个类时,它通常会首先向上委托。它会询问平台加载器,平台加载器再询问启动加载器,启动加载器尝试加载它。只有当没有父加载器能够加载它时,子加载器才会尝试。这就是委托模型,它防止了相同类以不同的名称被加载两次。

类标识及其工作原理

一个类由其名称定义它的加载器来标识。不同的加载器可以各自加载一个名为 com.example.MyClass 的类,JVM 将它们视为两种完全不同的类型。这听起来可能有些晦涩,但它是 Java 可以做的所有有趣事情的基础。

Bootstrap Loader: java.lang.Object (the core Object)
Application Loader: java.lang.Object (if somehow re-loaded by app loader)
→ Two different types, same name
→ This breaks your program, which is why delegation prevents it

自定义类加载器

程序可以定义自己的类加载器。自定义加载器可以从任何地方读取字节码:JAR 文件、网络、数据库,或它动态生成或转换的字节码。方法是:

  • 插件系统在编译时未知的情况下动态加载代码。
  • 应用服务器(Tomcat、JBoss)在同一JVM中运行多个应用程序,每个应用程序都有自己的类加载器。
  • Minecraft模组在加载时会修补游戏类。

为什么这对Minecraft模组很重要

模组存在是因为自定义类加载。一个Minecraft模组加载器会通过自己的自定义类加载器启动游戏,以便在加载时看到和修改游戏类。拦截点就是魔法发生的地方。

当加载器位于磁盘和JVM之间时,它可以在JVM定义类之前转换该类:

Disk: obfuscated Minecraft class
    ↓
Mod Loader (custom classloader)
    ↓ [bytecode transformation happens here]
    ↓
JVM: defines the patched class
    ↓
Memory: game runs with mods active

模组可以重写游戏方法,注入新行为,添加字段,而无需触及磁盘上的游戏JAR。JVM最终运行的是模组框架修改过的类。这正是Fabric、Forge和其他模组加载器修补Minecraft的方式。

延迟加载

并非所有类都在启动时加载。JVM在第一次实际使用类时加载该类。这被称为延迟加载

Program starts → JVM doesn't load String class yet
User calls System.out.println() → String is needed → Loader loads it

这意味着类加载器只需要找到并加载你的代码实际使用的类。未使用的类永远不会进入内存。这就是为什么你的Minecraft启动可能需要一段时间(模组添加了大量的类),但不会永远卡住的原因。

常见问题