一、JVM类加载总体了解
前5 步是固定的顺序并且也是类加载的过程,其中中间的3 步都属于连接,所以对于类加载来说总共分为以下几个步骤:
- 加载
- 连接(验证、准备、解析)
- 初始化
下面我们分别来看每个步骤的具体执行内容。
二、加载
加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载Class
Loading 是不同的,一个是加载Loading ,另一个是类加载Class Loading,所以不要把二者搞混了。
在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
三、连接
3.1 验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信 息被当作代码运行后不会危害虚拟机自身的安全。
验证选项: 文件格式验证、字节码验证、符号引用验证...
3.2 准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。
比如此时有这样一行代码:
public static int value = 123;
它是初始化 value 的 int 值为 0,而非 123。
3.3 解析
解析阶段是Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
四、初始化
初始化阶段,Java 虚拟机真正开始执行类中编写的Java 程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程。
总结:
Java程序最开始是一个 .Java 文件,经过编译成了 .class文件(字节码文件)。运行Java程序,JVM就会读取 .class 文件,把文件内容放到内存中,并且构造成 .class 对象。(类对象)
①加载:找到 .class 文件,打开文件读取内容,尝试解析格式。
②验证:检查一下当前 .class 文件的格式是否符合要求。
③准备:给类对象分配内存,并 “初始化”。
④解析:针对字符串常量的初始化。
⑤初始化:对类对象进行更具体的初始化操作。初始化静态成员、执行静态代码块、加载父类。
五、类加载过程中的双亲委派模型
5.1 什么是双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求 (它的搜索范围中没有找到所需的类) 时,子加载器才会尝试自己去完成加载。
- 启动类加载器:加载 JDK 中 lib 目录中 Java 的核心类库,即$JAVA_HOME/lib目录。 扩展类加载器。加载 lib/ext 目录下的类。
- 应用程序类加载器:加载我们写的应用程序。
- 自定义类加载器:根据自己的需求定制类加载器。
5.2 双亲委派模型对类加载的基本描述流程
- BootStrap ClassLoader : 负责加载标准库中的类
- Extension ClassLoader : 负责加载 JVM 扩展的库
- Application ClassLoader : 负责加载第三方库
- 从 Application ClassLoader 开始,不会立即就搜索第三方库的目录,而是先把加载任务委派给父类加载器,让父类尝试加载。
- 到了 Extension ClassLoader ,也是把加载任务委派给父类加载器,让父类尝试加载。
- 最后,到了 BootStrap ClassLoader , 也不想立即搜索标准库,但是它没有父类加载器,只能自己动手。如果找到了要搜索的类,就执行后续加载(就用不着下面的两个子类加载器了)。如果没找到,任务就会交给子类加载器。
- 此时任务回到了 Extension ClassLoader,就会搜索扩展库的目录,看是否有 .class 文件。若找到,执行后续加载。否则把任务交给子类加载器。
- 任务又回到了 Application ClassLoader ,就要搜索第三方库的目录,(往往是我们自己项目的目录) ,如果找到,执行后续加载,否则抛出一个异常。
这样做的目的,就是明确优先级,避免程序员的代码对标准库的代码产生负面影响。
那么一个类在什么时机会被加载呢?
- ①构造类的实例。
- ②使用类的静态方法/属性。
- ③子类的加载触发了父类的加载。
类加载之后,后续使用就不必加载了,类对象已经是现成的了。
5.3 双亲委派模型的优点
- 避免重复加载类:比如 A 类和 B 类都有一个父类 C 类,那么当 A 启动时就会将 C 类加载起来,那么在 B 类进行加载时就不需要在重复加载 C 类了。
- 安全性:使用双亲委派模型也可以保证了 Java 的核心 API 不被篡改,如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类,而有些 Object 类又是用户自己提供的因此安全性就不能得到保证了。
✨好啦,今天的分享就到这里!
🎉希望各位看官读完文章后,能够有所提升。
✨创作不易,还希望各位大佬支持一下!
👍点赞,你的认可是我创作的动力!
⭐收藏,你的青睐是我努力的方向!
✏️评论:你的意见是我进步的财富!