JVM-类加载机制

类加载的生命周期

加载(Loding)

验证(Verification)

准备(Preparation)

解析(Resolution)

初始化(Initialization)

使用(Using)

卸载(Unloading)

加载阶段

这个是类加载的第一个个阶段,主要完成三件事

  • 通过类的全限定名获取定义此类的二进制字节流
  • 将字节流所代表的静态存储结构转换为方法去运行时的数据结构
  • 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口

示例代码:

java 复制代码
// 加载阶段的示例
public class LoadingPhaseExample {
    public static void main(String[] args) throws Exception {
        // 当执行这行代码时,String类开始进入加载阶段
        Class<?> stringClass = String.class; // 或者 Class.forName("java.lang.String")

        // 此时类被加载到JVM中,但尚未初始化
        System.out.println("类已加载: " + stringClass.getName());
    }
}

验证阶段

确保Class文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

验证阶段的目的

  • 确保安全性:验证字节码的正确和安全性
  • 格式检查:确保类文件符合JVM规范
  • 行为验证:防止恶意代码破坏JVM运行

主要步骤

1、文件格式验证

  • 魔数检查:验证CAFEBABE魔数
  • 版本号验证:检查类文件版本是否兼容
  • 结构完整性:验证常量池、字典表、方法表等结构

2、元数据验证

  • 类型检查:验证类、接口、字段、方法的语义正确性
  • 继承关系验证:检查extends和implements关系是否合法
  • 抽象类验证:确保抽象类和接口的实现符合规范

3、字节码验证

  • 操作数栈一致性:验证指令操作数栈类型匹配
  • 类型转换安全:检查类型转换是否合法
  • 控制流分析:验证程序控制流图的正确性

4、符号引用验证

  • 引用有效性:验证符号引用执行的目标是否存在
  • 访问权限:检查对目标的访问权限是否合法
  • 链接准备:为后续解析阶段做准备

验证的重要性

  • 安全防护:防止恶意代码攻击JVM
  • 稳定性保障:确保类加载过程的稳定性
  • 规范遵循:强制类文件遵循JVM规范要求

准备阶段

为类变量分配内存并设置类变量初始值(零值),这些变量所使用的内存都将在方法区中进行分配

示例程序:

复制代码
public class PreparationPhase {
    public static int value = 123; // 在准备阶段value被赋值为0,在初始化阶段才被赋值为123
    public static final int CONSTANT = "final"; // 常量在准备阶段就被赋值
}

主要任务

  • 静态变量内存分配:为类的静态变量分配内存空间
  • 默认值初始化:给静态变量设置默认初始值

具体操作

1、内存分配

  • 在方法区为类的静态变量分配内存
  • 为类的静态字段预留存储空间

2、默认值设置

  • 数值类型设置为0(如int为0,long为0L)
  • 引用类型设置为null
  • boolean类型设置为false;

3、常量处理

  • final static 常量直接复制实际值
  • 非final静态变量仅设默认值

注意事项

  • 此阶段不执行static代码快
  • 真正赋值操作在是初始化极端完成
  • 仅为静态变量分配内存,示例变量在对象实例化时分配

解析阶段

主要任务

  • 符号引用转换:将常量池中的符号引用转换为直接引用
  • 内存地址确定:找到目标在内存中的实际地址

具体操作内容

1、类或接口的解析

  • 解析类或接口的符号引用
  • 验证访问权限和继承关系

2、字段解析

  • 解析类中字段的符号引用
  • 确定字段在类实例中的偏移量

3、方法解析

  • 解析类中方法的符号引用
  • 确定方法在方法表中的索引

4、接口方法解析

  • 解析接口中方法的符号引用
  • 处理接口方法的多态性

5、方法类型和方法句柄解析

  • 解析方法类型信息
  • 创建方法句柄引用

解析时机

  • 静态解析:在类加载时完成的解析
  • 动态解析:在运行时根据需要进行的解析

优化策略

  • 延迟解析:按需解析,提高加载效率
  • 缓存机制:缓存解析结果,避免重复解析

初始化阶段

主要内容

  • 执行类构造器:运行类的<clinit>方法
  • 静态变量复制:执行静态变量的实际赋值操作
  • 静态代码块执行:执行类中的静态初始化块

具体操作

1、类构造器<clinit>方法执行

  • 由编译器自动生成
  • 包含静态变量复制语句和静态代码块
  • 按照源码中的顺序执行

2、静态变量初始化

  • 执行静态变量的实际赋值(区别于准备阶段的默认值)
  • 按照声明顺序进行初始化

3、静态代码块执行

  • 执行static{}代码快中的语句
  • 只在类加载时执行一次

触发条件

  • 首次主动使用:首次创建类实例、调用类的静态方法、访问静态字段等
  • 子类触发:子类初始化的时候若父类未初始化则先初始化父类
  • 反色调用:通过反射访问类时

执行特点

  • 线程安全:JVM保证类初始化过程的线程安全
  • 单次执行:每个类只会初始化一次
  • 父类优先:父类初始化完成后才进行子类的初始化

注意事项

  • 异常处理:初始化失败会抛ExceptionInIntializerError
  • 懒加载:并非在类加载时立刻初始化,而是按需初始化
  • 顺序性:静态变量、静态代码快按照源码顺序执行

类加载器的层次结构

应用程序类加载器(Application ClassLoader)

  • 也成为系统类加载器,是java类加载器层次结构中最常用的加载器
  • 职责范围:负责加载应用classpath路径下的类和资源

主要功能

  • 加载用户类:加载开发者便携的业务逻辑类
  • 加载第三方依赖:加载项目中引入的jar包
  • classpath管理:处理系统属性java.class.path指定路径

加载范围

  • 项目类文件:应用程序源代码编译后的.class文件
  • 依赖库:Maven/Gradle管理的笛梵放jar包
  • 配置文件:位于classpath下的资源文件
  • Web组件:Servlet、Filter等Web相关类

工作原理

  • 双亲委派:遵循双亲委派模式,先委托父类加载器尝试加载
  • 最后加载:当只有Bootstrap和Extension类加载器都无法加载的时候才自己加载
  • 缓存机制:已加载的类会被缓存,避免重复加载

特点优势

  • 灵活性:可以根据classpath动态加载类
  • 隔离性:与其他类加载器保持独立的命名空间
  • 可扩展性:支持自定义类加载器扩展

使用场景

  • 常规应用:标准java应用程序的主要类加载器
  • Web容器:作为Web应用的基础类加载器
  • 插件系统:配合自定义类加载器实现插件热部署

扩展程序类加载器(Extension ClassLoader)

  • java类加载器层次结构中的中间层加载器
  • 职责范围:负责加载java扩展类库,位于Bootstrap和Application类加载器之间

主要功能

  • 扩展库加载:加载JRE安装目录下的lib/ext目录中的类库
  • 系统属性支持:加载java.ext.dirs系统属性指定路径下的类库
  • 标准扩展API:提供java标准扩展功能的类加载

加载范围

  • 标准扩展库:如JCE(加密扩展)、JSSE(安全套接字扩展)等
  • 第三方扩展:厂商提供的java标准扩展实现
  • 扩展目录jar包:lib/ext目录下的所有jar文件

工作原理

  • 双亲委派:遵循双亲委派模型,先委托Bootstrap类加载器尝试加载
  • 中间位置:在类加载器层次中处于Bootstrap和Application之间
  • 自动发现:自动扫描扩展目录并加载其中的类库

状态变化

  • java8以及之前,正常使用扩展机制
  • java9+、扩展机制已被废弃,被Platform Classloader替代
  • 模块化系统:现代java版本使用模块系统管理平台模块

特点

  • 权限级别:权限高于Application ClassLoader,低于Bootstrap ClassLoader
  • 向后兼容:java8中仍然支持扩展目录机制
  • 过渡性质:现代java版本中已逐步被模块化系统取代

启动程序类加载器(Bootstrap ClassLoader)

  • java类加载器层次结构中的根加载器
  • 实现语言:有C/C++实现,而非java代码
  • 核心职责:负责加载java核心类库,是类加载器的起点

主要功能

  • 核心类库加载:加载java运行时核心类库(如rt.jar)
  • 基础类型支持:加载基本数据类型、Object类、String类等基础类
  • 系统类加载:加载java.lang.*、java.util.*等核心包中的类

加载范围

  • 核心jar包:$JAVA_HOME/jre/lib/目录下的核心类库
  • 关键类库:rt.jar、charsets.jar、resources.jar等
  • 虚拟机类:JVM内部运行所需的基础类

工作特点

  • 最高权限:拥有最高的类加载权限和信任级别
  • 启动时加载:在JVM启动时最先加载
  • 不可替代:无法被其他类加载器替代或绕过

安全机制

  • 核心保护:确保java核心API的安全性
  • 防止替换:防止用户自定义类替换核心类库
  • 信任边界:作为Java平台信任的边界

与其他类加载器的关系

  • 父级地位:是Extension和Application类加载器的父级
  • 双亲委派:所有类加载请求都会先委托给Bootstrao加载器
  • 层次顶端:位于类加载器承接结构的最顶端

类加载器的触发条件

主动触发

  • 创建类实例:使用new关键字创建对象的时候
  • 访问静态字段:读取或修改类的静态字段时(final修饰的常量除外)
  • 调用类的静态方法时
  • 反射调用:通过反色访问类的时候
  • 子类初始化:初始化子类的时候检测父类没有初始化的话会先初始化父类

JVM启动触发

  • 主类加载:JVM启动时加载指定的主类
  • 系统类预加载:JVM启动时预加载核心系统类

接口特殊条件

  • 接口实现:当接口实现类被初始化时
  • 接口字段访问:访问接口中定义的字段的时候

动态加载

  • 自定义加载:通过ClassLoader.loadClass()方法主动加载
  • 热部署:运行时动态更新类定义

被动使用情况

  • 数组创建:创建某类的数组时不需要初始化该类
  • 常量访问:访问final修饰的静态常量时不触发初始化
相关推荐
好学且牛逼的马8 小时前
从“大师杰作”到“并发基石”:JUC(java.util.concurrent)发展历程与核心知识点详解(超详细·最终补全版)
jvm
知识即是力量ol9 小时前
Java 虚拟机:JVM篇
java·jvm·八股
Zzz 小生10 小时前
LangChain Tools:工具使用完全指南
jvm·数据库·oracle
wuqingshun31415911 小时前
什么是浅拷贝,什么是深拷贝,如何实现深拷贝?
java·开发语言·jvm
专注前端30年16 小时前
【Java高并发系统与安全监控】高并发与性能调优实战:JVM+线程池+Redis+分库分表
java·jvm·redis
星火开发设计1 天前
序列式容器:deque 双端队列的适用场景
java·开发语言·jvm·c++·知识
Anastasiozzzz2 天前
深入理解JIT编译器:从基础到逃逸分析优化
java·开发语言·jvm
小同志002 天前
JVM 类加载
jvm·jvm类加载
Hx_Ma162 天前
测试题(四)
java·开发语言·jvm
闻哥2 天前
Java虚拟机内存结构深度解析:从底层原理到实战调优
java·开发语言·jvm·python·面试·springboot