一、类加载过程
类加载指的是Java进程运行的时候,需要把 .class文件从硬盘读取到内存,并进行一系列的校验解析的过程。
下面我们来看一下类加载的执行流程。
对于⼀个类来说,它的生命周期是这样的:
其中前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步都属于连接,所以对于类加载
来说总共分为以下几个步骤:
1、加载
2、连接
- 验证
- 准备
- 解析
3、初始化
下面我们来看一下每个步骤的具体执行内容。
1、加载
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的⼀个阶段,它和类加载 Class Loading 是不同的。
在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:
1)通过⼀个类的全限定名(带有包名的类名)来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成⼀个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2、验证
验证是连接阶段的第⼀步,
这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求
,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
验证选项:文
件格式验证,
字节码验证,符号引用验证...
3、准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。
比如此时有这样一行代码:
public static int value = 123;
它是初始化 value 的 int 值为 0,而非123。
4、解析
解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。(该阶段主要是针对类中的字符串常量进行处理)
对符号引用替换为直接引用解释:
对于下面代码:
class Test {
private String s = "hello";
}
.class文件中会包含 “hello"。
对于上述代码,可以知道 s 变量里相当于保存了"hello"字符串常量的地址。但是,文件中不存在 “地址” 这样的概念,虽然没有地址,可以存储一个类似于地址的“偏移量”这样的概念。
所以,此处文件填充给 s 的是 hello 偏移量,就可以认为是符号引用。
接下来,要把 .class 文件加载到内存中,就会先把 "hello" 字符串加载到内存中,此时 hello 就有地址了。
接下来,s 里面的值就可以替换成 “hello”的真实地址(直接引用)了。
5、初始化
初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。初始
化阶段就是执行类构造器方法的过程。(该阶段针对类对象完成后续的初始化,还要执行静态代码块的逻辑,还可能会触发父类的加载)
二、双亲委派模型
站在 Java 虚拟机角度来看,只存在两种不同的类加载器:⼀种是启动类加载器(BootstrapClassLoader),这个类加载器使用 C++ 语言实现,是虚拟机自身的⼀部分;另外⼀种就是其他所有的 类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。站在 Java 开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK 1.2 以来,Java ⼀直保 持着三层类加载器、双亲委派的类加载架构器。
1、什么是双亲委派模型
简单地说,双亲委派模型描述了如何查找 .class 文件的策略。
JVM中进行类加载的操作,是有一个专门的模块,称为“类加载器”。
JVM中的类加载器默认是有三个的,也可自定义:
- Bootstrap ClassLoader:负责查找标准库中的目录
- Extension ClassLoader:负责查找扩展库中的目录
- Application ClassLoader:负责查找当前项目的代码目录以及第三方库的目录
上述三个类加载器存在“父子关系”,类似于二叉树,存在一个指针(引用)指向自己的 “父”类加载器。
2、双亲委派模型的工作过程
对于上图的工作过程:
总结:
如果⼀个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每⼀个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
双亲委派模型这一工作过程设定的主要目的:
确保类加载器的优先级;也可避免自己写的类不小心和标准库中的类重名,导致标准库中的类功能失效。
双亲委派模型的优点 :
1、
避免重复加载类:比如 A 类和 B 类都有⼀个父类 C 类,那么当 A 启动时就会将 C 类加载起来,那
么在 B 类进行加载时就不需要在重复加载 C 类了。
2、
安全性:使用双亲委派模型也可以保证了 Java 的核心 API 不被篡改,如果没有使用双亲委派模
型,而是每个类加载器加载自己的话就会出现⼀些问题,比如我们编写⼀个称为java.lang.Object
类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类,而有些 Object 类又是用户提供的,因此安全性就不能得到保证了。