如果有遗漏,评论区告诉我进行补充
相似的问题
- 什么是类加载器,类加载器有哪些?
面试官: 说一下类加载的执行过程?
我回答:
在Java中,类的加载是一个重要的过程,它是由Java虚拟机(JVM)的类加载器系统负责的。类加载的过程不仅仅包括加载类的字节码到内存中,还包括链接和初始化等步骤。下面我将详细介绍类加载的整个执行过程。
类加载的阶段
类加载过程可以分为三个主要阶段:加载(Loading)、链接(Linking)和初始化(Initialization)。这三个阶段是按照顺序进行的,其中链接阶段又可以细分为验证(Verification)、准备(Preparation)和解析(Resolution)三个子阶段。
加载(Loading)
- 定义:加载是类加载过程的第一步,在这个阶段,JVM需要通过类的全限定名(即包含包名的类名)来找到对应的字节码文件,并将其加载到JVM的内存中。加载过程中,JVM会创建一个代表该类的
java.lang.Class
对象,作为程序访问该类的各种信息的入口。 - 实现:通常由类加载器(ClassLoader)完成,包括根类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和系统类加载器(System ClassLoader)等。
- 加载阶段由类加载器完成,主要任务包括:
- 通过一个类的全限定名(全路径名)来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在Java堆中生成一个代表这个类的
java.lang.Class
对象,作为方法区这些数据的访问入口。
链接(Linking)
连接阶段分为验证(Verification)、准备(Preparation)和解析(Resolution)三个子阶段。
- 验证(Verification):确保被加载的类的正确性,即字节码文件的正确性,以及是否符合JVM规范等。这是为了确保JVM不会因为加载错误的类而破坏JVM的稳定性。
- 文件格式验证:验证字节流是否符合Class文件格式规范。
- 元数据验证:对字节码描述的信息进行语义分析,以确保其描述的信息符合Java语言规范的要求。
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:确保解析动作能正确执行。
- 准备(Preparation):为类的静态变量分配内存,并设置其初始值(注意,这里不是将变量赋值为代码中指定的值,而是赋予其数据类型的零值,如int为0,对象引用为null等)。
- 解析(Resolution):将类、接口、字段和方法的符号引用替换为直接引用的过程。即把代码中通过类名、方法名等访问类的内容的方式转换为JVM内部的引用地址。将常量池内的符号引用替换为直接引用。符号引用类似于C语言中的“远指针”,而直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
初始化(Initialization)
- 定义:类初始化阶段是类加载过程的最后一步,主要执行类构造器
<clinit>()
方法的过程。<clinit>()
方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块(static block)中的语句合并产生的。这个阶段才会执行用户定义的初始化操作(即静态初始化块中的代码和静态变量的赋值操作)。 - 特点:
<clinit>()
方法不需要显示调用,并且在多线程环境下被JVM保证只执行一次。 - 初始化阶段是类加载过程的最后一步,也是类加载过程中最复杂的一环。在此阶段,虚拟机会执行类构造器
<clinit>()
方法。<clinit>()
方法由编译器自动收集类中的所有类变量的赋值动作和静态初始化块中的语句合并生成。<clinit>()
方法与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()
方法执行前,父类的<clinit>()
方法已经被执行完毕。
类加载器
类加载器负责将类加载到JVM中。Java中的类加载器主要有以下几种:
- 启动类加载器(Bootstrap ClassLoader):负责加载
JAVA_HOME/lib
目录中的类库或-Xbootclasspath参数指定的路径中的类库,它是用C++语言实现的,是JVM自身的一部分。 - 扩展类加载器(Extension ClassLoader):负责加载
JAVA_HOME/lib/ext
目录中的类库或-Djava.ext.dirs参数指定的路径中的类库,它是用Java语言实现的,父类加载器为启动类加载器。 - 应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)所指定的类,它是用Java语言实现的,父类加载器为扩展类加载器。
类加载的时机
类加载的时机是在类首次使用时,即当JVM发现某个类还没有被加载时才去加载它。具体的触发条件包括:
- 创建类的实例;
- 访问或修改类的静态变量;
- 调用类的静态方法;
- 反射(如
Class.forName()
); - 初始化某个类的子类;
- JVM启动时被标明为启动类的类。
双亲委派模型
Java类加载器遵循双亲委派模型(Parent Delegation Model),即如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把请求委托给父类加载器去完成,每一个层次的类加载器都是如此。只有当父类加载器无法完成此加载请求(它的搜索范围中没有找到所需的类)时,子类加载器才会尝试自己去加载。
总结
类加载过程遵循了“加载-连接-初始化”的顺序,其中连接阶段又进一步细分为验证、准备和解析三个子阶段。这一过程确保了Java类能够安全、有序地被加载到JVM中,使得Java程序能够正确地使用这些类。同时,这一机制也支持了Java的动态加载和运行时动态类型创建等高级特性。