类加载器与类的加载过程
- 加载流程
字节码文件 | 类加载子系统 | ||
---|---|---|---|
字节码文件 | 加载阶段 | 链接阶段 | 初始化阶段 |
字节码文件 | 引导类加载器 | 验证 | 初始化 |
字节码文件 | 扩展类加载器 | 准备 | 初始化 |
字节码文件 | 系统类加载器 | 解析 | 初始化 |
- 类加载器子系统负责从文件系统或者网络中加载 Class 文件,class 文件在文件的开头有
特定的文件标识
- ClassLoader 只负责 class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。
- 加载的类信息存放与一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量地信息,可能还包括字符串字面量和数字常量(这部分常量信息是 class 文件中
常量池
部分的内存映射。
类加载器 ClassLoader 角色
s=>start: 开始
in1=>inputoutput: Car.class
e=>end: Car实例(car1,car2,...)
loader=>operation: ClassLoader
load=>operation: 加载并初始化
class=>operation: Car Class
instance=>operation: 实例化
s->in1(right)->loader->load(right)->class(right)->instance(right)->e
- class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到 JVM 当中来根据这个文件实例化出 n 个一模一样的实例。
- class file 加载到 JVM 中,被称为 DNA 元数据模板,放在方法区。
- 在 .class 文件-> JVM -> 最终成为元数据模板,此过程就要一个运输工具(类加载器 ClassLoader),扮演一个快递员的角色。
类的加载过程
- 代码
|
|
- 流程
s=>start: 开始
e=>end: 结束
con1=>condition: 装载类 HelloLoader 了吗?
con2=>condition: ClassLoader 装载顺利
op1=>operation: 链接
op2=>operation: 初始化 HelloLoader
op3=>operation: 调用 HelloLoader.main()
op4=>operation: 抛出异常
s->con1
con1(yes)->op1->op2(right)->op3->e
con1(no)->con2
con2(yes)->op1
con2(no)->op4
- 全流程
graph LR
A(加载 Loading)==>B(验证 Verification)==>C(准备 Preparation)-->D(剖析 Resolution)==>E(初始化 Initialization)
加载(Loading)
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的 java.lang.Class 对象
,作为方法区这个类的各种数据的访问入口
链接(Linking)
验证(Verify)
- 目的在于确保 class 文件的字节流中包含信息符合当前的虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
- 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
准备(Prepare)
- 为类变量分配内存并且设置该类变量的默认初始值,即零值。
这里不包含用 final 修饰的 static, 因为 final 在编译的时候就会分配了,准备阶段会显示初始化
。这里不会为实例变量分配初始化
, 类变量会分配在方法区中,而实例变量是会随着对象一起分配到 Java 堆中。
解析(Resole)
- 将常量池中的符号引用转换为直接引用的过程
- 事实上,解析操作往往会伴随着 JVM 在执行完初始化之后再执行。
- 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java虚拟机规范》的 class 文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
- 解析动作主要针对类或者接口、字段、类方法、接口方法、方法类型等,对应常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
初始化
- 初始化阶段就是执行类构造器方法
<clinit>()
的过程。 - 此方法不需要定义,是 javac 编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
- 构造器方法中指令按语句在源文件中出现的顺序执行。
<clinit>()
不同于类的构造器。(关联:构造器是虚拟机视角下的<init>()
)- 若该类具有父类,JVM 会保证子类的
<clinit>()
执行前,父类的<clinit>()
已经执行完毕。 - 虚拟机必须保证一个类的
<clinit>()
方法在多线程下被同步加锁。
几种类加载器
- 类加载器分为引导类加载器以及自定义加载器
- 自定义加载器又分为系统加载器,扩展类加载器以及自己写的加载器
|
|
虚拟机自带的加载器
-
启动类加载器(引导类加载器,Bootstrap ClassLoader)
- 这个类加载使用
c/c++语言实现的
,嵌套在 JVM 内部 - 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar 或 sun.boot.class.path 路径下的内容),用于提供 JVM 自身需要的类
- 并不继承 java.lang.ClassLoader,没有父加载器。
- 加载扩展类和应用类加载器,并指定为他们的父类加载器。
- 处于安全考虑,Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类。
- 这个类加载使用
-
扩展类加载器(Extention ClassLoader)
- Java 语言编写,由
sun.misc.launcher$ExtClassLoader
实现 - 派生于
ClassLoader
类 - 父类加载器为启动类加载器
- 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 的安装目录的 jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载。
- Java 语言编写,由
-
应用程序类加载器(系统类加载器,AppClassLoader)
- java 语言编写,由
sun.misc.launcher$AppClassLoader
实现 - 派生于
ClassLoader
类 - 父类加载器为扩展类加载器
- 它负责加载环境变量
classpath
或系统属性java.class.path
指定路径下的类库 - 该类加载是程序中默认的类加载器,一般类说,java 应用的类都是由它来完成加载
- 通过
ClassLoader#getSystemClassLoader()
方法可以获取到该类加载器
- java 语言编写,由