Java 类的加载过程主要分为五步:加载、验证、准备、解析、初始化。其中验证、准备、解析可以合称为连接。此外,这五步的顺序并不是完全固定的,比如为了支持动态绑定,解析的过程可以放在初始化之后。类的加载过程如下图所示:
加载过程主要做三件事情:
校验阶段主要确保 Class 文件字节流中的内容不会违反当前 JVM 的规范,不会危害到 JVM 运行时的安全。主要验证的有文件格式、元数据、字节码、符号引用。
准备阶段主要是将为类变量分配内存,并初始化为默认值。以下面的片段为例:
public static int value = 111;
需要注意的是,在准备阶段对于 int 类型,初始默认值为 0 而不是 111。同样的,其他基本类型的初始默认值都是该基本类型的默认值(如 double 的 0.0)。将 value 赋值为 111 的操作在初始化的步骤(即 clinit 方法)中进行。
解析是将符号引用转换为直接引用的过程。
执行类构建方法 clinit 的过程。clinit 方法由所有类变量的赋值动作和静态语句块 static{} 合并而来,这其中也包含了父类的 clinit 方法(类变量赋值动作与父类的静态语句块),同时在执行一个类的 clinit 方法时,也会通过递归方式保证其父类的 clinit 方法先被调用。
此外对于初始化阶段,只有几种情况才会要求类立刻执行 clinit 方法:
ClassLoader 中有一个 ClassLoader parent,记录其父类加载器。根类加载器 bootstrap ClassLoader 是最顶层的 ClassLoader,没有父类加载器。类加载器的加载范围不同,如果子类加载器想要加载父类加载器已经加载的类,可以通过双亲委派机制,直接访问父类加载器已经加载的类。 但是有的时候父类加载器也需要加载子类加载器的 Class,这时候就需要打破双亲委派机制,主要方式是使用 Thread 类里的线程上下文类加载器的方法 setContextClassLoader。