类加载的生命周期
加载(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修饰的静态常量时不触发初始化