🔴 类加载过程概述 #JVM/类加载
🔴 类加载过程是JVM将类的二进制字节流通过加载、链接、初始化三个阶段,转化为运行时数据区中可用组件的完整过程。整个过程涉及方法区、堆内存、虚拟机栈等多个内存区域的协作。
🟠 方法区运行时数据结构详解
🟠 在加载阶段,字节流所代表的静态存储结构会转化为方法区中的以下运行时数据结构:
🟢 1. 类型信息 (Type Information)
- 🟢 类的全限定名:如
java.lang.String - 🟢 直接父类的全限定名:继承关系信息
- 🟢 实现的接口列表:接口实现关系
- 🟢 类的访问修饰符:public、final、abstract等
- 🟢 类的类型:普通类、接口、枚举、注解等
🟡 2. 常量池 (Constant Pool)
- 🟡 字面量:字符串常量、数值常量、类名常量等
- 🟠 符号引用:类和接口的全限定名、字段名和描述符、方法名和描述符
- 🟡 运行时常量池:动态解析后的直接引用
🟢 3. 字段信息 (Field Information)
- 🟢 字段名称:字段的标识符
- 🟢 字段类型:基本类型或引用类型
- 🟢 字段修饰符:public、private、static、final等
- ⚪ 字段在类中的位置:字段索引或偏移量
🟢 4. 方法信息 (Method Information)
- 🟢 方法名称:方法的标识符
- 🟢 返回类型:方法的返回值类型
- 🟢 参数列表:参数类型和数量
- 🟢 方法修饰符:访问级别、静态、抽象等
- 🟡 方法的字节码:JVM指令序列
- ⚪ 异常表:方法可能抛出的异常类型
🟡 字节码相关面试问题
🔴 高频问题:
- "JVM是如何执行Java代码的?"
- Java源码 → 编译为字节码(.class文件) → JVM加载字节码 → 解释执行或编译为机器码
- 字节码是JVM的中间语言,跨平台的关键
- "什么是字节码?为什么Java被称为'一次编译,到处运行'?"
- 字节码是Java编译后的中间代码,不依赖具体硬件和操作系统
- 一次编译生成字节码,在不同平台的JVM上都能运行
🟠 中频问题:
- "你能解释一下new、getstatic、putstatic这些字节码指令的作用吗?"
new:创建对象实例getstatic:获取静态字段值putstatic:设置静态字段值- 这些都是触发类初始化的字节码指令
- "为什么说Java是解释执行的语言?"
- JVM解释器逐条解释字节码指令执行
- 现代JVM结合了解释执行和即时编译(JIT)两种方式
🟡 低频问题:
- "如何查看一个类的字节码?"
- 使用
javap -c命令反编译查看字节码 - 使用IDE插件或在线工具查看
- 使用
- "字节码指令有哪些分类?"
- 加载和存储指令:如iload、istore
- 运算指令:如iadd、isub
- 类型转换指令:如i2l、l2i
- 对象创建和操作指令:如new、getfield
🟡 5. 类变量 (Class Variables)
- 🟡 静态变量存储空间:为static变量分配的内存区域
- ⚪ 变量初始化状态:标记变量是否已初始化
- 🟡 变量值:静态变量的实际值
🟡 6. 指向类加载器的引用 (ClassLoader Reference)
- 🟡 加载该类的ClassLoader实例:记录是哪个ClassLoader加载了该类
- 🔴 类加载器层次关系:支持双亲委派模型 #JVM/双亲委派
🟠 7. 指向Class对象的引用 (Class Object Reference)
- 🟠 Class对象的引用:作为访问方法区数据的入口点
- 🟠 反射支持:通过Class对象进行反射操作
- 🟠 类型检查:instanceof操作的基础
🔴 五个阶段详解
🔴 1. 加载 (Loading)
🔴 目的: 将类的二进制字节流加载到内存中
🔴 具体过程:
- 🔴 通过类的全限定名获取类的二进制字节流
- 🔴 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 🔴 在内存中生成一个代表该类的Class对象,作为方法区这个类的各种数据的访问入口
🟡 实现方式:
- 🟡 本地文件系统加载
- 🟡 从JAR包中加载
- 🟡 通过网络获取
- 🟡 通过自定义ClassLoader实现特殊加载逻辑
🟡 关键点: ClassLoader类提供了findClass()方法,通过重写该方法可以实现自定义的类加载逻辑
🔴 2. 链接 (Linking)
🟠 2.1 验证 (Verification)
- 🟠 确保Class文件的字节流中包含的信息符合当前虚拟机要求
- 🟠 验证文件格式、元数据、字节码、符号引用等
🟠 2.2 准备 (Preparation)
- 🟠 为类的静态变量分配内存
- 🟠 将静态变量初始化为默认值(零值)
- 🟠 注意:这里设置的是默认值,不是程序中的初始值
🟠 2.3 解析 (Resolution)
- 🟠 将常量池内的符号引用替换为直接引用
- 🟠 符号引用:以一组符号来描述所引用的目标
- 🟠 直接引用:直接指向目标的指针、相对偏移量或间接定位到目标的句柄
🔴 3. 初始化 (Initialization)
🔴 目的: 执行类的初始化代码的过程
🔴 触发条件:
- 🔴 遇到new、getstatic、putstatic或invokestatic这4条字节码指令
- 🔴 使用反射调用类时
- 🔴 当初始化一个类时,发现其父类还没有进行初始化
- 🔴 虚拟机启动时,用户指定的要执行的主类
🔴 执行内容:
- 🔴 执行类构造器
<clinit>()方法 - 🔴 初始化静态变量为程序中指定的值
- 🔴 执行静态代码块
🟡 4. 使用 (Using)
🟡 目的: 类的正常使用阶段
🟡 运行时行为:
- 🟡 通过Class对象创建实例时,在堆中分配对象内存
- 🟡 方法执行时在虚拟机栈中创建栈帧
- 🟡 局部变量和操作数栈在栈帧中运作
- 🟡 字节码指令由执行引擎解释执行
- 🟡 程序计数器指引执行顺序
- 🟡 调用本地方法时启用本地方法栈
🟠 5. 卸载 (Unloading)
🟠 目的: 回收类占用的内存空间
🟠 卸载条件:
- 🟠 该类的所有实例都已经被回收
- 🟠 加载该类的ClassLoader已经被回收
- 🟠 该类对应的Class对象没有被引用
🟠 注意: 类卸载条件苛刻,实际应用中很少发生
浙公网安备 33010602011771号