类加载过程及双亲委派模型

发布于:2025-07-23 ⋅ 阅读:(11) ⋅ 点赞:(0)

目录

一、类加载过程

1、加载

(1)通过一个类的全限定名来获取定义这个类的二进制字节流。

(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

(3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

2、验证

(1)文件格式验证:

(2)元数据验证:

(3)字节码验证:

(4)符号引用验证:

3、准备:

4、解析:

 (1)符号引用:

(2)直接引用:

5、初始化:

二、双亲委派模型

1、双亲委派模型工作原理

(1)核心原理:自底向上委托

(2)工作流程:

2、双亲委派模型的优点

(1)保证了类加载的安全性。

(2)保证了类的唯一性。

 三、总结


一、类加载过程

       类加载的过程是一个严谨且有序的过程,每个阶段都有其特定的任务和意义,这五个阶段通常是按顺序开始的,但在实际执行过程中,各个阶段可能会交叉进行。

1、加载

在加载阶段,java虚拟机需要完成这三件事情

(1)通过一个类的全限定名来获取定义这个类的二进制字节流。

这里的二进制字节流可以从多种来源获取,比如从本地文件系统中的.class 文件读取、从 JAR 包中读取、通过网络下载、由其他文件生成(如 JSP 文件被编译成.class 文件)等。

(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

方法区是 JVM 用于存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的内存区域。 

(3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

这个 Class 对象就像一把钥匙,有了它我们才能访问到方法区中该类的相关信息。 

        加载阶段可以使用系统提供的类加载器来完成,也可以由用户自定义的类加载器来完成。用户自定义类加载器可以通过继承 ClassLoader 类并重写 findClass 方法来实现对特定来源的类的加载。 

2、验证

       验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,保证这些信息不会危害虚拟机自身的安全,除此之外将这里的内容转为结构化的数据。

(1)文件格式验证:

       验证字节流是否符合 Class 文件格式的规范,只有通过了这个阶段的验证,字节流才会进入方法区中存储,后续的验证阶段都是基于方法区中的存储结构进行的,不再直接操作字节流。

(2)元数据验证:

       对类的元数据信息进行语义校验,保证不存在不符合 Java 语言规范的元数据信息。

比如检查这个类是否有父类(除了 java.lang.Object 之外,所有的类都应当有父类)、这个类的父类是否继承了不允许被继承的类(被 final 修饰的类)、如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法等。

(3)字节码验证:

       这是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

它会对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为,例如保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,不会出现类似于 “在操作数栈放置了一个 int 类型的数据,使用时却按 long 类型来加载到本地变量表中” 这样的情况。

(4)符号引用验证:

       发生在虚拟机将符号引用转化为直接引用的时候,这个阶段在解析阶段中进行。它的目的是确保解析动作能正常执行,验证符号引用中通过字符串描述的全限定名是否能找到对应的类、在指定类中是否存在符合方法的字段描述符和简单名称所描述的方法和字段等。 

3、准备:

       准备阶段是正式为类中定义的变量(即静态变量,被 static 修饰的变量)分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。

注:这里所说的初始值通常情况下是数据类型的零值,比如 int 类型的零值是 0,boolean 类型的零值是 false 等。例如,对于类变量public static int value = 123;,在准备阶段,value 会被初始化为 0,而不是 123,把 value 赋值为 123 的动作是在初始化阶段完成的。如果类变量被 final 修饰,那么在准备阶段就会为其赋值

4、解析:

       解析阶段是Java虚拟机将常量池内的符号引⽤替换为直接引⽤的过程,也就是初始化常量的过程。

 (1)符号引用:

        以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,它与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

(2)直接引用:

        可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄,它与虚拟机实现的内存布局相关,如果有了直接引用,那引用的目标必定已经在内存中存在。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。解析过程会根据不同的符号引用类型采取不同的解析方式。

5、初始化:

       初始化阶段,Java虚拟机真正开始执行类中编写的Java程序代码,将主导权移交给应⽤程序。初始化阶段就是执行类构造器方法的过程。 

针对类对象的各种属性进行填充,包括类中的静态成员;

如果这个类还有父类,并且父类还没有加载,那么此环节也会触发父类的类加载

二、双亲委派模型

      双亲委派模型是Java类加载机制的核心设计原则,它定义了类加载器在加载类时的层级协作方式。它要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。

1、双亲委派模型工作原理
(1)核心原理:自底向上委托

      当类加载器收到加载请求时,首先不会自己尝试加载,而是将请求逐级向上委托给父类加载器,每一个层次的类加载器都是如此。

      因此所有的加载请求最终都应该传到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己去加载。

(2)工作流程:

      进行类加载通过全限定类名找.class的时候,就会把ApplicationClassLoader作为入口,然后不断向上委托直到BoostrapClassLoader向上委托时发现他的父亲为null,只能自己进行类加载,根据类名找标准库范围是否存在匹配的.class文件 。

如果BoostrapClassLoader没有找到,就把任务向下交代给孩子,然后孩子继续寻找。

2、双亲委派模型的优点
(1)保证了类加载的安全性

       通过双亲委派模型,Java 核心类库(如 java.lang.Object)不会被自定义的类所替代。比如,如果我们自定义了一个名为java.lang.Object的类,由于双亲委派模型,加载这个类的请求会被委派给启动类加载器,而启动类加载器加载的是 JDK 中的java.lang.Object类,这样就避免了自定义的类篡改核心类库的情况。​

(2)保证了类的唯一性

       同一个类在不同的类加载器环境下可能会被认为是不同的类,但通过双亲委派模型,只要是同一个类(全限定名相同),最终都会由同一个类加载器(通常是父类加载器)来加载,这样就保证了类在 JVM 中的唯一性,避免了因类的重复加载而导致的各种问题。 

 三、总结

       类加载过程是 JVM 将类的字节流加载到内存并进行处理,最终形成可执行代码的过程,包括加载、验证、准备、解析和初始化五个阶段,每个阶段都有其独特的作用,共同保证了类能够正确地被 JVM 识别和使用。​

       双亲委派模型则是类加载器遵循的重要机制,它通过层级委派的方式,让类加载请求从子加载器向父加载器传递,只有父加载器无法完成加载时,子加载器才会尝试加载。这一模型保证了类加载的安全性和类的唯一性,是 Java 程序稳定运行的重要基础。​

        理解类加载过程和双亲委派模型,对于我们深入掌握 Java 虚拟机的运行机制、排查类加载相关的问题以及进行相关的开发工作都具有重要的意义。