摘要: 本文主要是关于类加载器的原理和应用。
【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings
类加载器
类加载器根据类的二进制名(binary name)读取 Java 编译器编译好的字节码文件(.class 文件),生成一个 Class 类的实例。之后,JVM 用 Class 实例来生成类的对象。
下面是 Java 语言规范定义的二进制名的一些例子:
1 | java.lang.String |
类加载器的分类
java.lang.ClassLoader
是一个抽象的类,几乎所有的类加载器都是该类的实例。JVM 在执行 Java 代码时,至少会用到三种类加载器:
(1) 引导类加载器
引导类加载器是在 JVM 运行时内嵌在 JVM 中的一段用来加载 Java 核心类库的特殊 C++ 代码。Java.lang.String 类就是由引导类加载器加载的。
引导类加载器不是用 Java 代码编写的,所以它并不是 ClassLoader 类的实例,且没有父级。在 HotSpot 虚拟机中用 null 表示引导类加载器。
(2) 平台类加载器
平台类加载器用于加载平台类,可以用作 ClassLoader 实例的父级。平台类包括 Java SE 平台 API、它们的实现类,以及由平台类加载器或其祖先定义的特定于 JDK 的运行时类。
(3) 系统类加载器
系统类加载器又被称为应用程序类加载器。其通常用于在应用程序类路径、模块路径,以及特定于 JDK 的工具上定义类,我们自己编写的 Java 类通常都是由此类加载器完成加载的。平台类加载器是系统类加载器的父级或祖先。
java.lang.ClassLoader
相关的 API
ClassLoader 中有两个静态方法,用于得到平台类加载器和系统类加载器的实例。
1 | public static ClassLoader getPlatformClassLoader() |
如果要得到某个加载器的父级类加载器,则可以调用 getParent 方法:
1 | // 如果类加载器的父级是引导类加载器,那么 getParent 方法将返回 null。 |
每个 Class 对象都包含对定义它的类加载器的引用。Class
类的 getClassLoader
方法可以得到定义该类的类加载器:
1 | public ClassLoader getClassLoader() |
示例代码
1 | public class PrintClassLoader { |
结果:
1 | 平台类加载器: jdk.internal.loader.ClassLoaders$PlatformClassLoader@4517d9a3 |
类加载器的加载机制
当 JVM 要加载某个类时,JVM 会先指定一个类加载器,负责加载此类。
而被指定的类加载器在尝试去根据某个类的二进制名查找其对应的字节码文件并定义之前,会首先委托给其父级加载器(getParent 方法返回的类加载器)尝试加载,如果加载失败,就会由自己来尝试加载此类。
在一般情况下,这个由 JVM 指定的类加载器就是系统类加载器,JVM 会自动调用其 loadClass 方法来开启类的加载过程,如下图:
这个加载机制就称为类加载的双亲委托模型,即由不同的类加载器负责加载特定的类。
类加载采用委托模型,可以保证 Java 核心类库的安全,即保证由引导类加载器加载的类不能被用户随便替换,用户不能自己随便定义一个名为 java.lang.String 的类来替换 Java 核心类库的 java.lang.String 类,否则会抛出 ClassCastException。
自定义类加载器
可以继承 ClassLoader 类,来扩展 Java 虚拟机动态加载类的方式。有几个应用场景:
- 你的字节码的来源不是文件
- 为了防止别人反编译你编写的类,对字节码文件做了混淆或加密
- 需要自定义的类加载器来加载类
findClass
方法
自定义类加载器只需要重写 findClass
方法即可:
1 | protected Class<?> findClass(String name) throws ClassNotFoundException |
loadClass
方法
ClassLoader 类中有一个 loadClass 方法,使用指定的二进制名加载类,返回一个 Class 对象,与 Class.forName 方法的作用类似。
在 ClassLoader 实例上调用 loadClass 方法,会自动调用 findClass 方法。
defineClass
方法
在重写的 findClass 方法中,我们可以采用任何方式来得到一个类的字节码数据,然后调用 ClassLoader 类中的一个 final 方法 defineClass 将存放字节码数据的字节数组转换为 Class 类的实例。:
1 | protected final Class<?> defineClass(String name, byte[] b,int off, int len) throws ClassFormatError |
例子: 从文件中加载类的类加载器
在 findClass 中,我们先调用了辅助方法 loadData,然后调用 defineClass 方法将保存字节码数据的字节数组转换为 Class 类的实例。
在 loadData 辅助方法中,我们通过读取字节码文件的方式加载类。
1 | import java.io.File; |
我们以文章 使用 Class 和反射创建类的对象 中的 Person 类为例。假设这个类已经经过编译生成了 Person.class。我们通过自定义的类加载器来加载这个类。
1 | import java.lang.reflect.Constructor; |
结果:
1 | class Person |