# ClassLoader类加载器

class 文件是类的二进制数据常见的存储方式，我们需要使用 ClassLoader 来将其加载到内存中，对其进行验证、解析、初始化，并在这一过程中创建`java.lang.Class`实例。\
在典型的 Java 虚拟机 Hotspot 中，`Class`实例和`String`常量会保存在堆区（Heap），类的元数据（Metadata）会保存在元数据区（Metaspace）。

## 类加载过程

类的加载过程可分为加载、链接、初始化三个步骤。

### 加载

加载是类加载过程中最重要的一步，绝大多数的 CoreMod 修改 class 文件也是在这一步进行的。\
首先 ClassLoader 根据给定的类名寻找类的二进制数据，这个数据可能是磁盘上的 class 文件，也可能是通过代码生成出来的二进制数据，自定义 ClassLoader 往往覆写了寻找这一数据的方法 `findClass` ，以便对类进行修改或自定义 ClassPath 以外的类来源。\
然后将其存储进方法区，最后创建`Class`实例作为方法区的访问入口。每个类的 `Class` 实例在 ClassLoader 中需要被妥善缓存，在尝试加载已有的类时应当返回现有的 `Class` 实例，这样可以保证单一 ClassLoader 中每个类最多被加载一次。

### 链接

链接还可以分为验证、准备、解析三步。 首先验证文件格式、元数据、字节码，然后为静态变量分配内存以及使用各类零值给予默认值，最后将符号引用替换为直接引用。

### 初始化

静态变量初始化赋值和`static`代码块会被编译为`<clinit>`方法，初始化阶段会调用这个方法。\
如果 ClassLoader 实现过程中通过恰当的缓存机制保障了在每个类最多被加载一次，也可以保障类的初始化也最多被执行一次。

## 双亲委派模型

双亲委派模型简单来说就是 ClassLoader 受到加载类的请求时，首先询问自己的父类能否进行加载，如果父类无法加载，再自己尝试加载。\
在典型的 Hotspot Java 8 虚拟机中，一般分为用于加载用户类的 `AppClassLoader`、加载平台类的`ExtClassLoader`、加载系统类的`BootstrapLoader`，后者优先级高于前者。\
不过，Java 程序加载所使用的 ClassLoader 理应对开发者是透明的，ClassLoader 的实现可能会随时发生变化，例如 Java 9 就不再使用 `URLClassLoader` 作为 `AppClassLoader` 的类型、 `ExtClassLoader` 替换为 `PlatformClassLoader`，如果我们拘泥于其具体的实现，在程序开发中难免会因此遇到错误。我们应当关注应用程序类是被多个 ClassLoader 使用双亲委派模型加载的，并在自定义 ClassLoader 妥善使用这个模型，不建议覆写 `loadClass` 方法，这个方法是双亲委派模型的具体实现。\
双亲委派模型最大的优点是保障 ClassLoader 各司其职，极大程度的避免了类被错误、重复加载。

## 自定义 ClassLoader

通过对类加载的简单介绍，我们了解了基本的类加载流程，可以在这基础上对基本的 ClassLoader 进行扩展，实现自定义 ClassLoader 。\
自定义 ClassLoader 是 ModLauncher、LaunchWrapper 动态修改 Minecraft 的基础。\
自定义 ClassLoader 一般有以下用途：

* 动态修改类（CoreMod常用）
* 动态加载类（例如动态解密类、加载网络类）
* 隔离加载类（例如ModLauncher将CoreMod与Minecraft隔离，防止不当类加载）

自定义 ClassLoader ，需要继承 `ClassLoader` 类或其子类（例如 `URLClassLoader`），一般会覆写以下方法中的一个或多个：

* `findClass`，这个方法接收一个类名，需要实现寻找这个类并返回对应的 `Class` 对象，再次强调需要做好缓存，切忌重复加载类
* `loadClass`，这个方法接收一个类名，需要实现加载这个类并返回对应的 `Class` 对象，也再次强调需要实现双亲委派模式

推荐覆写 `findClass` 而不是 `loadClass` ，前者只需要完成寻找类的二进制数据，并进行 `defineClass` ，后者需要妥善处理双亲委派模型。
