JVM 类加载子系统

本文详细介绍了JVM中的类加载子系统,从class文件结构开始,涵盖魔数、版本号、常量池等关键部分。接着讨论了类加载的时机,包括预加载、懒加载等场景。类加载过程包括加载、验证、准备、解析和初始化。文章还阐述了JVM启动时main方法的调用流程。重点介绍了双亲委派模型,解释了其防止类重复加载和保护核心类安全的作用。最后,提到了JDK自带的类加载器和SPI服务提供者接口的概念及其应用。
摘要由CSDN通过智能技术生成


----------------------------------------------------
class 文件
一个类或接口的定义信息, 字节码格式, 类似 C 语言的结构体, 主要记录了类, 父类, 接口, 字段, 方法等信息

class 文件的结构
------------------------------------
魔数
-------------------
版本号
-------------------
常量池
主要存放 字面量 符号引用

字面量
文本字符串,被申明为 final 的常量值

符号引用
编译原理方面的概念
-------------------
类 / 接口 的全限定名
-------------------
字段的名称和描述符
-------------------
方法的名称和描述符
-------------------
方法句柄
-------------------
方法类型
-------------------
访问标志 
类或接口,是否 public, 是否 abstract, 是否 final
-------------------
类索引,父类索引,接口索引集合
-------------------
字段表集合
用于描述接口或类中声明的变量,包括类级变量和实例级变量
-------------------
方法表集合
用于描述接口或类中声明的方法
-------------------
属性表集合
class文件, 字段表, 方法表都用到了属性表集合来描述某些场景专有的信息
------------------------------------
类加载的时机
预加载
JVM 启动时会加载一些核心类,比如 java.lang.String

懒加载
1. 构造该类的实例 new
2. 首次访问类的静态属性或静态方法
3. 使用子类时会触发父类的加载
4. 反射访问,Class.forName
----------------------------------------------------
类加载的过程
加载 -> 验证 -> 准备 -> 解析 -> 初始化

类的生命周期
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
连接 =(验证 -> 准备 -> 解析)

加载
1.根据类名获取定义此类的二进制字节流
2.在方法区建立此类对应的运行时数据结构
3.在 java 堆中实例化一个该类的 class 对象

验证
1.文件格式验证
保证字节流能被正确的解析,并存储于方法区
2.元数据验证
确保数据类型符合 java 语言规范
3.字节码验证
确保被验证类的方法运行时安全
4.符号引用验证
确保依赖的类,方法,字段都能被正常访问

准备
为静态变量分配内存并设置初始值,0 或 常量值
没有 final 修饰的 static 变量赋零值
final 修饰的 static 变量赋常量值

解析
虚拟机将常量池内的符号引用替换为直接引用的过程

初始化
执行类构造器 <clinit>()
类构造器 由编译器收集类中 静态变量赋值,和静态语句块合并产生
----------------------------------------------------
JVM 启动 - main 方法的调用
1.加载启动类 (Main-Class)
通过 java 命令启动, 其后的参数会有 Main-Class, 启动 jar 包, jar 包的 manifest 配置文件中有配 Main-Class
2.JVM 对这个启动类 执行 链接 和 初始化
3.调用初始类中的 main() 方法
4.执行 main() 方法
链接(预加载) 或 加载 使用其他的 类/接口
----------------------------------------------------
双亲委派模型 - 加载类的规则
一个类加载器收到加载请求, 先查找缓存,有则返回,没有则委派给父加载器, 一层层向上委派, 直到最顶层的启动类加载器, 
启动类加载器缓存中没有就到加载路径中查找,有则加载返回,没有则由子加载器加载,一层层向下查找
1.避免重复加载
比如, java.lang.Object, rt.jar 里的类, 无论哪一个类加载器要加载这个类, 最终都是委派给模型最顶端的启动类加载器加载, 确保 Object 类在内存中只有一个, 如果不使用双亲委派模型, 各个类加载器自行加载, 同一个类在内存中就有多个
2.避免核心类被篡改或替换
通过双亲委派模型, 核心类只能由启动类加载器加载, 加载的时机, 类的全限定名, 路径都定死了, 你想加载一个同名的类, 加载不了
----------------------------------------------------
jdk 自带了三个类加载器

启动类加载器 bootstrap class loader
负责加载 <JAVA_HOME>\jre\lib 目录下的 jdk 核心类库, rt.jar, tools.jar

扩展类加载器 extension class loader
负责加载 <JAVA_HOME>\jre\lib\ext 目录下的扩展类库

应用程序类加载器 application class loader / 系统类加载器
负责加载用户类路径 ClassPath 下所有的类
----------------------------------------------------
SPI - service provider interface
虚拟机层面的服务注册与发现的机制, 通过服务消费方和服务提供方解耦, 动态扩展应用
约定如下
1. 在 jar 包的 meta-inf/services 包下, 以接口全限定名为文件名, 文件内容是实现类
2. 接口和实现类都放在 classpath 路径下
3. 服务消费方通过 java.util.ServiceLoader 动态装载实现模块

案例
jdbc
servlet 初始化器
spring 容器
Dubbo
----------------------------------------------------
破坏双亲委派模型
jdbc
DriverManager 是启动类加载器加载的, 它会通过 ServiceLoader 加载 mysql 的驱动, 而 ServiceLoader 会去拿线程上下文类加载器, 拿到的是系统类加载器, 最终 mysql 的驱动是通过系统类加载器加载的, 这种行为实际上是父加载器请求子加载器完成类加载,违背了双亲委派模型的规则
----------------------------------------------------
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我三师弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值