Java类加载的三个阶段
《Loading, Linking, and Initializing》
加载(Loading)
将Java字节码数据读取到JVM中,并映射为 Class 对象。
数据源可以是 jar文件、class文件或网络数据源。
用户可以实现自己的类加载器来自定义加载过程。
链接(Linking)
把原始的类定义信息平滑地转化入JVM。
分为三个子步骤:
验证(Verification)
这是JVM安全的重要保障。
JVM会核验字节信息是否符合 Java 虚拟机规范,防止恶意信息或不合规的信息危害JVM运行。
验证阶段可能会触发更多 class 的加载。
准备(Preparation)
创建类或接口中的静态变量,并初始化静态变量的初始值。
此步骤侧重于分配内存空间,不会去执行更进一步的JVM指令;与后续的“初始化”不同。
解析(Resolution)
将常量池中的 符号引用(Symbolic Reference)替换为直接引用。
初始化(Initialization)
真正执行类初始化的代码逻辑,包括 对静态字段进行赋值、执行静态代码块。
这些代码逻辑事先由编译器在编译阶段整理好。父类型的初始化 优先于 当前类型。
Parent Delegation Model(“双亲委派模型”)
其实翻译为“双亲”很容易误导中文读者。(在学习“树”的数据结构时,我们会把“parent node”翻译为“父节点”)
此处的委派模型指:当一个ClassLoader需要加载一个类时,会尝试将该任务委托给它的 父ClassLoader,除非父ClassLoader找不到目标资源。
目的:这种委派模型的目的是为了避免重复加载Java类型。
这体现了类加载机制的“可见性”和“单一性”。
可见性:子ClassLoader可见到父ClassLoader加载的类型(反之不能)。
单一性:子ClassLoader不会加载父ClassLoader已经加载过的类型。
但是“邻居”ClassLoader之间,同一类型仍然可以被加载多次;因为它们互相不可见。
Java 8 及以前的 类加载器
三个内建的 类加载器
Bootstrap Class-Loader(启动类加载器)
加载 jre/lib 目录下的 jar包。如,rt.jar。
Extension Class-Loader(扩展类加载器)
加载 jre/lib/ext 目录下的 jar包。
Application Class-Loader(应用类加载器)
加载 classpath 下的资源
Java 9 的类加载器
Jigsaw 项目为 Java 9 引入了 Java平台模块化系统(JPMS)。Java SE的源码被划分为一系列模块,类加载器也发生了非常大的变化。
如,扩展类加载器被重命名为“平台类加载器”(Platform Class-Loader);rt.jar 被移除,JDK核心类库被存储在 jimage 文件中。
新增的 Layer 抽象可以更方便地实现类似容器的逻辑。
内建类的加载器都在 BootLayer 中;
其它Layer内部有自定义的类加载器;
不同版本模块可以同时工作在不同的 Layer中。
自定义类加载器
使用场景
绝大多数应用都不需要实现自定义的类加载器。一般在以下场景中会用到自定义类加载器:
-
实现“进程内隔离”。
类加载器作用于不同的命名空间,它可以提供类似 容器/模块化 的效果。
如,两个模块分别依赖于某个类库的不同版本,让这两个模块分别被不同的“容器”加载就可以互不干扰。
-
从不同的数据源获取类定义信息。
如,应用需要从网络数据源获取类信息,而不是本地文件系统
-
需要自己操作字节码,动态修改或生成类型。
实现方式
大致步骤:
通过指定的名称,找到其二进制实现(字节码),或修改/生成字节码。自定义类加载器一般就是在这做“定制”
创建 Class 对象,并完成类加载过程。通过 ClassLoader.defineClass 方法将二进制信息转换为 Class 对象。
加快java类信息加载的技术
Java类的加载、解释、编译都需要时间,这会明显导致Java应用启动变慢。
类加载器获得的类信息是字节码,它与平台无关,具体用于执行时还需要解释/编译。
AOT(Ahead-of-Time Compilation)
这项技术是提前把Java Class提前编译为本地代码(native code),以加速Java应用的启动时间。
目前还只是试验性的特性,局限性比较大。
AppCDS(Application Class-Data Sharing)
这项技术可以将Java Class信息存储在文件系统中,作为 Shared Archive。
JVM会通过内存映射技术映射到相应的地址空间,免除类加载、解析等开销,加速启动,同时也可以减少内存占用(footprint)。
局限性:如果存在大量运行时动态类加载,此技术的帮助会很有限