Java 通过引入字节码和 JVM 机制,提供了强大的跨平台能力。那么 JVM 又是如何加载我们的 class 类的呢?
一般来说,我们把 Java 的类加载过程分为三个主要步骤:
-
1.加载
-
2.链接
-
3.初始化
上述具体行为在 Java 虚拟机规范中已有明确定义,可参考链接:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
首先是加载阶段(Loading),它是 Java 将字节码数据文件从不同的数据源读取到 JVM 中,并映射成 JVM 认可的数据结构(Class 对象),这里说的数据源可能是各种各样的形态,如 jar 文件,class 文件,也有可能是网络数据源等;如果输入的数据不是 ClassFile 的结构,则会抛出 ClassFormatError.
Note: 加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。
然后到第二阶段,链接阶段(Linking), 这是比较核心的步骤,简单说是把原始的类定义信息平滑的转入 JVM 运行的过程。而这一过程又可以细分为三步:
-
1.验证(Verification), 这是虚拟机安全的重要保障,JVM 需要核验字节信息是否符合 Java 虚拟机规范,若不符合,则 VerifyError, 这样就防止了恶意信息或者不合规矩的信息危害 JVM 运行。
-
2.准备(Preparation), 创建类或接口中的静态变量,并初始化静态变量的初始值。注意,这里的初始化有别于下面的显式初始化阶段,侧重点在于分配所需的内存空间,不会进一步去执行 JVM 指令。
-
3.解析(Resolution), 这一步会将常量池中的符号引用替换为直接引用。
最后一步是初始化阶段(intialization), 这一步真正去执行类的初始化代码逻辑,包括静态字段赋值的动作,以及静态代码块内的逻辑,编译器在编译阶段就会把这部分的逻辑整理好,另外,父类的初始化逻辑优先于当前类。
再说说双亲委派模型,简单来说就是当前类加载器视图加载某个类型的时候,除非父类加载器找不到相应类型,否则尽量将这个任务交给父类加载器去完成。它的目的是为了避免重复加载 Java 类型。