----------------------------------------------------
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 的驱动是通过系统类加载器加载的, 这种行为实际上是父加载器请求子加载器完成类加载,违背了双亲委派模型的规则
----------------------------------------------------