类加载机制

类的加载

其中类加载的过程包括了加载、验证、准备、解析、初始化、使用、卸载七个阶段。验证、准备、解析三个阶段统称为连接。

在这七个阶段中, 加载、验证、准备和初始化和卸载这五个阶段发生的顺序是确定的, 而解析阶段则不一定, 它在某些情况下可以在初始化阶段之后开始, 这是为了支持 Java 语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始, 而不是按顺序进行或完成, 因为这些阶段通常都是互相交叉地混合进行的, 通常在一个阶段执行的过程中调用或激活另一个阶段。

加载

加载时类加载过程的第一个阶段, 在加载阶段, 虚拟机需要完成以下三件事情:

  1. 通过一个类的全限定名来获取其定义的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在 Java 堆中生成一个代表这个类的 java.lang.Class 对象, 作为对方法区中这些数据的访问入口。

类加载器中将详细描述使用类加载器加载类的过程

连接

  • 验证:是否有正确的内部结构, 并和其他类协调一致
  • 准备:负责为类的静态成员分配内存, 并设置默认初始化值
  • 解析:将类的二进制数据中的符号引用替换为直接引用

初始化

类会在首次被 “主动使用” 时执行初始化, 为类(静态)变量赋予正确的初始值。在 Java 代码中, 一个正确的初始值是通过类变量初始化语句或者静态初始化块给出的。

初始化一个类包括两个步骤:

  • 如果类存在直接父类的话, 且直接父类还没有被初始化, 则先初始化其直接父类
  • 如果类存在一个初始化方法, 就执行此方法

注:初始化接口并不需要初始化它的父接口。

类加载器

基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

Java 中的类加载器大致可以分成两类, 一类是系统提供的, 另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  • 引导类加载器(bootstrap class loader):负责加载 $JAVA_HOME 中 jre/lib/rt.jar 里所有的 class 或 Xbootclassoath 选项指定的 jar 包。由 C++ 实现, 不是 ClassLoader 子类。
  • 扩展类加载器(extensions class loader):负责加载 java 平台中扩展功能的一些 jar 包, 包括 $JAVA_HOME 中 jre/lib/ext/*.jar 或 -Djava.ext.dirs 指定目录下的 jar 包。
  • 应用类加载器(App class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说, Java 应用的类都是由它来完成加载的。

JVM 中除了根类加载器之外的所有类的加载器都是 ClassLoader 子类的实例, 通过重写 ClassLoader 中的方法, 实现自定义的类加载器。

双亲委派模型

除了引导类加载器之外, 所有的类加载器都有一个父类加载器, 可以通过 getParent()方法得到。于系统提供的类加载器来说, 应用类加载器的父类加载器是扩展类加载器, 而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说, 其父类加载器是加载此类加载器 Java 类的类加载器。

它们之间的层次关系被称为类加载器的双亲委派模型

双亲委派模型过程

某个特定的类加载器在接到加载类的请求时, 首先将加载任务委托给父类加载器, 依次递归, 如果父类加载器可以完成类加载任务, 就成功返回;只有父类加载器无法完成此加载任务时, 才自己去加载。

使用双亲委派模型的好处在于 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object, 它存在在 rt.jar 中, 无论哪一个类加载器要加载这个类, 最终都是委派给处于模型最顶端的 Bootstrap ClassLoader 进行加载, 因此 Object 类在程序的各种类加载器环境中都是同一个类。相反, 如果没有双亲委派模型而是由各个类加载器自行加载的话, 如果用户编写了一个 java.lang.Object 的同名类并放在 ClassPath 中, 那系统中将会出现多个不同的 Object 类, 程序将混乱。因此, 如果开发者尝试编写一个与 rt.jar 类库中重名的 Java 类, 可以正常编译, 但是永远无法被加载运行。