JVM 类加载全过程

最近在准备面试,正把平时积累的笔记、项目中遇到的问题与解决方案、对核心原理的理解,以及高频业务场景的应对策略系统梳理一遍,既能加深记忆,也能让知识体系更扎实,供大家参考,欢迎讨论。

一、类加载生命周期

JVM 中类的生命周期分为七个阶段:

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载

其中前五个阶段通常统称为 类加载过程
在这里插入图片描述
在这里插入图片描述
举一反三:类加载的流程类比到工作中处理渠道方计费数据的流程

类加载阶段数据处理对应步骤说明
加载 Loading下载数据到本地 / 读取文件到内存把外部资源引入系统,类似 JVM 从硬盘读取字节码
验证 Verification校验数据合法性检查格式、字段类型、业务规则是否正确,保证安全可靠
准备 Preparation设计变量 / 初始化内存空间给处理变量分配存储空间并赋初始值,为后续解析做好准备
解析 Resolution按行解析数据 / 将原始数据映射到结构化对象把原始数据转化为程序可用的内部表示
初始化 Initialization给对象赋值 / 封装批量数据按业务规则给字段赋值,组装成可操作的批量数据
使用 Using后续业务处理 / 保存数据库数据真正被业务使用或写入存储
卸载 Unloading释放内存 / 清理对象内存释放,避免占用,类似 JVM 卸载类和 ClassLoader

二、各阶段详解

1. 加载(Loading)

  • 通过类的 全限定名 获取字节码。
  • 在硬盘上查找 .class(.java编译的结果) 文件并通过 I/O 读取。
  • JVM 会把字节码文件从硬盘读取进内存,并在方法区(JDK 8 之后叫元空间(Metaspace),使用本地内存)中建立对应的运行时数据结构,包括类的元信息(类名、父类、接口)、字段表、方法表和运行时常量池等,这样类才具备在使用期间被实例化、方法调用和数据访问的基础
  • 在堆中生成 java.lang.Class 对象作为访问入口。
  • 触发条件:调用 main() 方法、new 对象、访问类的静态成员等。

2. 验证(Verification)

  • 目的确保字节码文件合法、安全,如果发现问题(比如非法指令、越界访问、破坏 JVM 约束),就会拒绝加载,从而避免危害虚拟机的稳定性和安全性。正常我们写的程序都不会被拒绝的。

  • 检查内容

    1. 文件格式验证。
    2. 元数据验证。
    3. 字节码验证。
    4. 符号引用验证。

3. 准备(Preparation)

  • 为类的静态变量分配内存,并设零值
  • 注意:并不是赋代码中的值,而是赋默认值。
public static int a = 10;
// 准备阶段:a = 0
// 初始化阶段完成赋值:a = 10

4. 解析(Resolution)

  • 将常量池中的 符号引用 替换为 直接引用
  • 属于 静态链接过程
  • 对应的 动态链接:在运行时(方法调用时)才进行。

5. 初始化(Initialization)

  • 执行类的初始化逻辑:

    1. 按照源码顺序,为静态变量赋值。
    2. 执行静态代码块。
public class Demo {
    static int a = 10;
    static { a = 20; }
}
// 初始化后 a = 20

6. 使用(Using)

  • 类进入正常使用阶段:

    • 调用类静态方法(如 main())。
    • new 对象。
    • 调用对象实例方法。

7. 卸载(Unloading)

  • 当 ClassLoader 和类实例都不再被引用 时,JVM 会回收它们,触发类卸载。类卸载可以释放方法区(或元空间)占用的内存,支持动态部署和更新。

三、整体回顾

  • 加载:读取 .class,生成 Class 对象。
  • 验证:确保安全合法。
  • 准备:静态变量赋零值。
  • 解析:符号引用 → 直接引用。
  • 初始化:赋初始值,执行静态块。
  • 使用:new 对象,方法调用。
  • 卸载:Class 对象回收。
### JVM 类加载机制的详细过程 JVM(Java虚拟机)类加载机制是Java运行时的核心部分之一,它负责将类的字节码文件加载到内存中,并将其转换为JVM可以使用的运行时数据结构。类加载机制的工作原理涉及多个阶段,这些阶段共同构成了类的生命周期。 #### 类的生命周期 一个类从被加载到虚拟机开始,直到卸载出内存为止,其生命周期通常经历以下七个阶段: 1. **加载**(Loading):这是类加载过程的第一步,JVM会通过类的全限定名来获取类的二进制字节流,并将其加载到内存中。 2. **验证**(Verification):确保加载的类的字节码是合法的,并且不会危害JVM的稳定性。 3. **准备**(Preparation):为类的静态变量分配内存,并设置初始值(通常为零值,而非代码中定义的值)。 4. **解析**(Resolution):将类、接口、字段和方法的符号引用转换为直接引用。 5. **初始化**(Initialization):执行类的初始化代码(如静态代码块和静态变量赋值)。 6. **使用**(Using):程序使用类创建对象、调用方法等。 7. **卸载**(Unloading):类不再被使用,JVM回收其占用的内存。 其中,前五个阶段(加载、验证、准备、解析、初始化)统称为“类的加载过程”[^3]。 #### 类加载器的作用 JVM中的类加载器负责具体的类加载工作,常见的类加载器包括: - **启动类加载器**(Bootstrap ClassLoader):负责加载Java核心类库(如`rt.jar`)。 - **扩展类加载器**(Extension ClassLoader):负责加载Java的扩展类库(位于`jre/lib/ext`目录下)。 - **应用程序类加载器**(Application ClassLoader):负责加载应用程序的类路径(CLASSPATH)中的类。 #### 双亲委派机制 JVM默认采用**双亲委派机制**来加载类。其工作原理如下: 1. 当一个类加载器收到类加载请求时,它首先会检查自己的缓存中是否已经加载过该类。 2. 如果没有加载过,则将请求委托给父类加载器进行处理。 3. 这个过程递归进行,直到顶层的启动类加载器。 4. 如果父类加载器无法加载该类,则子类加载器尝试自己加载。 双亲委派机制的核心目的是**避免重复加载**,并**保证类的唯一性**。通过这种方式,可以确保JVM中每个类只被加载一次[^4]。 #### 类加载的具体步骤 1. **加载**(Loading): - 通过类的全限定名获取类的二进制字节流。 - 将字节流转化为方法区的数据结构。 - 在内存中生成一个代表该类的`java.lang.Class`对象,作为方法区这个类的数据访问入口。 2. **验证**(Verification): - 确保字节码的合法性,包括文件格式验证、元数据验证、字节码验证等。 3. **准备**(Preparation): - 为类的静态变量分配内存,并初始化为默认值(例如,`int`类型的变量初始化为0)。 4. **解析**(Resolution): - 将常量池中的符号引用转换为直接引用。例如,将类中的方法调用转换为内存地址。 5. **初始化**(Initialization): - 执行类的初始化代码,包括静态代码块和静态变量的显式赋值。 #### 示例代码 以下是一个简单的Java类加载示例,展示了如何通过类加载器加载一个类: ```java package classloader.test.bean; public class TestBean { public TestBean() { System.out.println("TestBean constructor called."); } } ``` 在程序运行时,JVM会通过类加载器将`TestBean`类加载到内存中,并按照上述流程完成类的加载和初始化[^2]。 #### 打破双亲委派机制 虽然双亲委派机制是JVM的默认行为,但在某些特殊场景下(例如热部署、插件化开发等),需要打破这种机制。可以通过自定义类加载器并重写`loadClass`方法来实现。具体做法包括: - 重写`loadClass`方法,改变类加载的委托顺序。 - 直接调用`findClass`方法加载类,而不委托给父类加载器。 以下是一个简单的自定义类加载器示例: ```java public class CustomClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 读取类的字节码文件 byte[] classData = loadClassData(name); if (classData == null) { throw new ClassNotFoundException(); } return defineClass(name, classData, 0, classData.length); } private byte[] loadClassData(String className) { // 实现从文件或网络加载类的字节码逻辑 return new byte[0]; } } ``` 通过这种方式,可以实现类的自定义加载逻辑,从而打破双亲委派机制[^5]。 #### 类加载机制的意义 JVM类加载机制具有以下重要意义: - **动态性**:类在需要时才会被加载,提高了程序的灵活性。 - **安全性**:通过验证阶段确保类的合法性,防止恶意代码破坏JVM。 - **唯一性**:通过双亲委派机制保证类的唯一性,避免重复加载。 - **可扩展性**:支持自定义类加载器,方便实现热部署、插件化等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值