⭐ 作者:小胡_不糊涂
🌱 作者主页:小胡_不糊涂的个人主页
📀 收录专栏:JavaEE
💖 持续更文,关注博主少走弯路,谢谢大家支持 💖
类加载
- [1. 加载](#1. 加载)
- [2. 验证](#2. 验证)
- [3. 准备](#3. 准备)
- [4. 解析](#4. 解析)
- [5. 初始化](#5. 初始化)
- [6. 双亲委派模型](#6. 双亲委派模型)
对于⼀个类来说,它的⽣命周期是这样的:
1. 加载
"加载"(Loading)阶段是整个"类加载"(Class Loading)过程中的⼀个阶段,它和类加载
Class Loading 是不同的,⼀个是加载 Loading 另⼀个是类加载 Class Loading。
在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:
- 通过⼀个类的"全限定名"来获取定义此类的⼆进制字节流
- 将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构
- 在内存中⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区这个类的各种数据的访问⼊⼝
也就是找到.class文件,打开文件,读取文件内容。
当代码中给定某个类的"全限定类名",比如:java.lang.String
,java.util.ArrayList
,然后jvm就会根据这个类名在一些指定的目录范围内查找。
2. 验证
验证是连接阶段的第⼀步,这⼀阶段的⽬的是确保Class⽂件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运⾏后不会危害虚拟机⾃⾝的安全。
验证选项:
- ⽂件格式验证
- 字节码验证
- 符号引⽤验证
3. 准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存 并设置类变量初始值的阶段。
⽐如此时有这样⼀⾏代码:
java
public static int value = 123;
它是初始化 value 的 int 值为 0,⽽⾮ 123
只是分配内存空间,还没有初始化。此时这个空间上的内存的数值,就是全0的。
4. 解析
针对类对象中包含的字符串常量进行处理,进行一些初始化操作。也就是将常量池内的符号引用替换为直接引用。
比如:final String s="abc"
,当字符串常量 s 在编译之后,也会进入 .class 文件,同时在 .class 文件的二进制指令中,也会有一个 s 这样的引用被创建出来。我们知道引用里本质上保存的是一个变量的地址,但在.class文件中,不涉及到内存地址,因此在.class 文件中,s的初始化语句,就会先被设置成一个"文件的偏移量" ,通过偏移量,就能找到 "abc" 这个字符串所在的位置,当我们这个类真正被加载到内存中的时候,再把这个偏移量替换回真正的内存地址。
5. 初始化
初始化阶段,Java 虚拟机真正开始执⾏类中编写的 Java 程序代码,将主导权移交给应⽤程序。针对类对象进行初始化,初始化阶段就是执⾏类构造器⽅法的过程。
比如:把类对象中需要的各个属性都设置好;初始化好static成员;执行静态代码块;还可能需要加载一下父类等等。
6. 双亲委派模型
双亲委派模型属于类加载中第一个步骤"加载"过程中的一个环节。它的作用就是根据全限定名找到 .class文件。
解释 :类加载器是JVM中的一个模块,而JVM内置了三个类加载器。
当⼀个类加载器收到了类加载的请求,它⾸先不会⾃⼰去尝试加载这个类,⽽是把这个请求委派给⽗类加载器去完成,每⼀个层次的类加载器都是如此,因此所有的加载请求最 终都应该传送到最顶层的启动类加载器中,只有当⽗加载器反馈⾃⼰⽆ 法完成这个加载请求(它的搜索范围中没有找到所需的类)时,⼦加载器才会尝试⾃⼰去完成加载。
上图类加载的过程(找.class 文件的过程)
- 给定一个类的全限定类名,形如java.lang.String
- 从Application ClassLoader作为入口,开始执行查找的逻辑
- Application ClassLoader,不会立即去扫描自己负责的目录(负责的是搜索项目当前目录和第三方库对应目录)而是把查找的任务,交给它的父亲,Extension Classloader
- Extension ClassLoader也不会立即扫描自己负责的目录(负责的是 JDK中一些扩展的库,对应的目录)而是把查找的任务,交给它的父亲BootStrap ClassLoader
- BootStrap Classloader,也不想立即扫描自己负责的目录(负责的是标准库的目录), 也想把任务交给它的父亲,结果发现自己没有父亲!因此BootStrap ClassLoader 只能亲自负责扫描标准库的目录
- 没有扫描到,就会回到Extension ClassLoader。Extension ClassLoader就会扫描负责的扩展库的目录。如果找到,就执行后续的类加载操作,此时查找过程结束;如果没找到,还是把任务交给孩子来执行
- 没有扫描到,就会回到Application ClassLoader。Application ClassLoader就会负责扫描当前项目和第方库的目录。如果找到,就执行后续的类加载操作。如果没找到,就会抛出一个ClassNotFoundException
优点:
- 避免重复加载类:⽐如 A 类和 B 类都有⼀个⽗类 C 类,那么当 A 启动时就会将 C 类加载起来,那么在 B 类进⾏加载时就不需要在重复加载 C 类了。
- 安全性:使⽤双亲委派模型也可以保证了 Java 的核⼼ API 不被篡改,如果没有使⽤双亲委派模
型,⽽是每个类加载器加载⾃⼰的话就会出现⼀些问题,⽐如我们编写⼀个称为 java.lang.Object
类的话,那么程序运⾏的时候,系统就会出现多个不同的 Object 类,⽽有些 Object 类⼜是⽤⼾⾃⼰提供的因此安全性就不能得到保证了。