01-JVM类加载机制
type
status
date
slug
summary
tags
category
icon
password
1️⃣ 类加载过程
当使用java命令运行main函数时,JVM通过类加载器将主类加载到内存。
1.加载(Loading)
查找并读取类的字节码文件(
.class
文件)到内存。调用
main()
方法、创建对象(new
)、访问静态成员等。在方法区生成一个
java.lang.Class
对象,作为该类的元数据入口。通过类加载器(如
Bootstrap
、Extension
、Application
、ClassLoader
)从文件系统、JAR包或网络加载字节码。2.验证(Verification)
确保字节码文件的格式和内容符合JVM规范,防止恶意或错误代码。
检查内容:
- 文件格式(魔数、版本号)。
- 语义正确性(方法调用、变量访问)。
- 字节码指令合法性。
目的:保障安全性和正确性。
3.准备(Preparation)
为类的静态变量分配内存,并赋默认值(零值)。
仅处理静态变量,非静态变量在对象实例化时分配。
示例:
static int a = 100;
→ 在准备阶段,a被分配内存并初始化为0(默认值)。static final
常量除外,常量在编译期已确定值。4.解析(Resolution)
将符号引用替换为直接引用。
符号引用:类、方法、字段等的符号描述(如
main()
方法的字符串表示)。直接引用:指向实际内存地址的指针或句柄。
类型:
静态链接:类加载期间完成(如解析
main()
方法引用)。动态链接:运行时完成(如动态调用的方法)。
示例:将
MainClass.main
的符号引用替换为指向方法区中main方法的内存地址。5.初始化(Initialization)
执行类的初始化代码,为静态变量赋程序指定的值,运行静态代码块。
按代码中声明的顺序执行(从上到下)
执行内容:
- 静态变量的显式赋值(如
static int a = 100;
中的100)。
- 静态代码块(
static { ... }
)。
触发:类首次主动使用时(如调用
main()
、创建实例、访问静态成员)。6.使用(Using)
类被JVM加载后,程序开始使用类(如执行
main()
方法逻辑、创建对象等)。此阶段是类的实际运行期,可能涉及动态链接、对象分配等。
7.卸载(Unloading)
当类不再被引用且符合垃圾回收条件时,JVM卸载类,释放内存。
通常由垃圾回收器(GC)处理,开发者无需干预。
条件:
- 类的所有实例已被回收。
- 类的Class对象无引用。
- 类的类加载器已被回收。
运行main函数的类加载流程
- JVM启动,调用应用类加载器(Application ClassLoader)加载MainClass的字节码。
- 经过加载,在方法区创建MainClass的Class对象。
- 验证确保字节码合法(如检查
public static void main(String[] args)
签名)。
- 准备为MainClass的静态变量分配内存,赋默认值。
- 解析将main方法的符号引用替换为直接引用,指向方法区中main方法的代码。
- 初始化执行MainClass的静态变量赋值和静态代码块。
- JVM调用main方法,进入程序执行阶段(使用)。
- 程序结束后,若MainClass不再被引用,可能被卸载。
2️⃣ 类加载器和双亲委派机制
Java的类加载器采用双亲委派模型,按层级分工加载不同来源的类。
1.引导类加载器(Bootstrap ClassLoader)
职责:加载JVM核心类库,位于
<JAVA_HOME>/jre/lib
目录下的rt.jar
(包含java.lang.*
等)、charsets.jar
等。特点:
- 由C++实现,非Java类,无父加载器。
- 负责加载JVM运行所需的基础类(如Object、String)。
- 无法直接访问(
ClassLoader.getSystemClassLoader()
不返回它)。
示例:加载
java.lang.Object、java.util.ArrayList
。2.扩展类加载器(Extension ClassLoader)
职责:加载
<JAVA_HOME>/jre/lib/ext
目录下的扩展JAR包或指定的扩展类库。特点:
- 由Java实现,父加载器为引导类加载器(实际为null,表示引导类加载器)。
- 用于加载标准库之外的扩展功能。
示例:加载安全管理类(如
javax.crypto.*
)。3.应用程序类加载器(Application ClassLoader)
职责:加载ClassPath环境变量、-cp选项或JAR包中的类,主要加载用户编写的类。
特点:
- 默认加载用户代码(如运行main函数的类)。
- 由Java实现,父加载器为扩展类加载器。
- 也称为系统类加载器(
ClassLoader.getSystemClassLoader()
返回它)。
示例:加载MainClass或项目中的自定义类。
4.自定义类加载器(Custom ClassLoader)
职责:加载用户指定的非标准路径下的类或自定义加载逻辑。
特点:
- 继承
java.lang.ClassLoader
,重写findClass
或loadClass
方法。
- 用于特殊场景,如动态加载、加密类文件、热部署等。
- 父加载器通常为应用程序类加载器(视定义而定)。
示例:加载网络下载的字节码、插件系统中的类。
1.双亲委派模型
类加载器遵循双亲委派机制,确保类的唯一性和安全性:
- 收到加载请求时,先委托给父加载器(递归向上)。
- 父加载器无法加载时,子加载器才尝试加载。
- 最终由引导类加载器或合适的加载器完成加载。
优点:
- 安全性:防止用户自定义类覆盖核心类(如
java.lang.String
)。
- 唯一性:确保同一类只加载一次,避免冲突。
- 效率:优先加载核心类库,减少重复加载。
2.类加载器在运行main函数中的作用
- JVM启动,调用应用程序类加载器加载MainClass。
- 加载过程中,依赖的核心类(如String)由引导类加载器加载。
- 如果MainClass使用扩展库(如
javax.crypto
),由扩展类加载器加载。
- 自定义加载器通常不直接参与,除非MainClass位于非标准路径或动态加载。
- 加载完成后,进入验证、准备、解析、初始化阶段,最终执行main方法。
3.为什么要设计双亲委派机制?
沙箱安全机制
自己写的
java.lang.String.class
类不会被加载,这样便可以防止核心API库被随意篡改。避免类的重复加载
当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性。
4.打破双亲委派
为什么需要打破双亲委派?
某些场景下,双亲委派模型限制了灵活性,需通过自定义类加载器打破规则:
- 类隔离:不同模块或应用需加载同名但不同版本的类(如Web应用中的库冲突)。
- 动态加载:运行时加载外部字节码(如插件系统、热部署)。
- 版本管理:支持多个版本的同一类库共存(如不同Web应用依赖不同版本的Spring)。
- 特殊加载逻辑:加载加密类、远程类或自定义路径的类。
如何打破双亲委派?
打破双亲委派通常通过自定义类加载器,重写ClassLoader的loadClass方法或直接实现加载逻辑。
重写loadClass方法
- 默认情况下,
ClassLoader.loadClass
实现双亲委派逻辑。
- 重写后可自定义加载顺序,优先尝试本地加载而非委托父加载器。
- 示例代码:
直接调用defineClass
- 绕过loadClass,直接从字节码定义类。
- 常用于动态加载外部字节码(如插件或热部署)。
- 示例:
使用线程上下文类加载器
- 通过
Thread.setContextClassLoader
设置自定义类加载器,绕过默认加载器。
- 常用于框架(如JDBC、Spring)动态加载实现类。
- 示例:
典型场景与实现
1️⃣ Web容器(如Tomcat)
需求:每个Web应用需加载自己的依赖库(如不同版本的Spring),避免冲突。
实现:
- Tomcat使用WebappClassLoader为每个Web应用创建独立类加载器。
- 优先加载
WEB-INF/lib
和WEB-INF/classes
中的类,绕过双亲委派。
- 核心库(如Servlet API)仍由共享的CommonClassLoader加载。
效果:实现类隔离,同一服务器上多个Web应用可使用不同版本的类库。
2️⃣ 模块化框架(如OSGi)
需求:支持动态加载和卸载模块,模块间类隔离。
实现:
- 每个Bundle(模块)有独立的类加载器。
- 自定义加载逻辑,优先加载模块内部类,必要时导入其他模块的类。
效果:模块间互不干扰,支持热更新。
3️⃣ 热部署与插件系统
需求:运行时加载新类或更新类(如IDE插件)。
实现:
- 自定义类加载器从指定路径或网络加载字节码。
- 直接调用
defineClass
定义类,绕过父加载器。
4️⃣ SPI(Service Provider Interface)
需求:核心类(如JDBC驱动)需加载用户实现的类,但核心类由引导类加载器加载,无法访问用户类。
实现:
- 使用线程上下文类加载器加载用户实现的类。
- 示例:JDBC的DriverManager通过上下文类加载器加载第三方驱动。
3️⃣全盘负责委托机制
“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。
打破双亲委派意味着类加载器不再严格遵循“委托父加载器优先”的规则,而是由自定义类加载器全盘负责类的加载逻辑。这种方式常见于需要灵活控制加载行为的场景(如Web容器、模块化框架)。以下是“全盘负责”的机制和实现方式:
当打破双亲委派,自定义类加载器需要:
- 完全控制类查找路径:从特定目录、网络、数据库等加载字节码。
- 管理类隔离:确保同名类在不同上下文不冲突。
- 处理依赖:手动解析类的依赖关系(如加载依赖的其他类)。
- 验证与定义:确保字节码合法,并将其定义为Class对象。
上一篇
04-贪心算法
下一篇
02-JVM内存模型
Loading...