JVM 系列(二) --- JVM 类加载的基本流程

JVM 类加载

  • 1 JVM 类加载概述
  • 2类加载的基本流程
    • 2.1 加载(Loading)
    • 2.2 验证(Verification)
    • 2.3 准备(Preparation)
    • 2.4 解析(Resolution)
    • 2.5 初始化(Initialization)
    • 2.6 使用(Using)
  • 3 示例
  • 4 双亲委派模型
    • 4.1 概述
    • 4.2 三种类加载器
    • 4.3 三种类加载器执行说明
    • 4.4 双亲委派模型的优点

1 JVM 类加载概述

  Java 中的类加载是 JVM 中的一个非常核心的流程,其做的事情就是将 .class 文件转换成 JVM 中的对象, 例如我们发明了一个编程语言,那么肯定是想让这个编程语言跑起来, 那么这就需要把源代码编译成可执行程序,然后再去执行代码逻辑. 要想完成类加载,必须要明确的知道 .class 文件中都有啥,按照规则来进行解析,因此编译器和 JVM 类加载器必须要商量好 .class 文件的格式, 这个 .class 文件的格式是 Java 虚拟机规范文档里面约定的,其实就是一种"协议".关于 Java 虚拟机规范文档地址为: Java虚拟机规范文档.
在这里插入图片描述

2类加载的基本流程

  关于类加载的基本流程主要是从 .class 文件 => 内存类对象的过程,主要步骤如下:
JVM 系列(二) --- JVM 类加载的基本流程_第1张图片

2.1 加载(Loading)

 这里的加载阶段指的是整个类加载过程中的一个阶段, 这里千万不要与类加载混淆,一个是加载Loading,另一个是类加载 Class Loading; 首先将 .class 文件先找到, 代码中需要加载某个类,就需要在某个特定的目录中找到这个 .class 文件, 找到后打开这个文件并进行读取,此时就把这些数据读到了内存里面;总之,在加载 Loading 阶段, Java 虚拟机需要完成以下三件事情:
1) 通过一个类的全限定名来获取定义此类的二进制字节流;
2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3) 在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据的访问入口.

2.2 验证(Verification)

验证就是把刚才读到内存里的东西进行一个校验, 主要是验证一下刚才读到的这个内容是不是一个合法的 .class 文件, 必须是编译器生成的 .class 文件才能通过验证, 如果我们随便创建一个后缀名为 .class 文件是不能通过验证的; 并且这里除了验证文件格式之外,也会验证一下文件里面的一些字节码指令(方法里面具体要执行的指令)是否正确. 总之, 验证选项可以是文件格式验证, 字节码验证, 符号引用验证等.

2.3 准备(Preparation)

这里的准备阶段其实就是为了类对象中的一些成员分配内存空间, 并且进行一个初步的初始化操作, 也就是把初始空间设为全 0; 例如:

class Test {
    public static int value = 111;
}

类似于这句代码初始化 value 的 int 值为 0, 而不是111.

2.4 解析(Resolution)

  解析操作主要是针对字符串进行的处理, .class 文件中会涉及到一些字符串常量, 在这个类加载的过程中, 就需要把这些字符串常量给替换成当前 JVM 内部已经持有的字符串常量的地址. 也可以说解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程, 也就是初始化常量的过程. 这里需要注意的是: 并不是程序一启动, 就立即把所有的类都给加载了, 而是用到哪个类就加载哪个类, 但是呢字符串常量池是最初启动 JVM 就有的, 存在于堆中.

2.5 初始化(Initialization)

  这里的初始化才是对静态变量进行初始化, 同时也会执行 上面代码中 static 代码块. 也就是说 Java 虚拟机真正开始执行类中编写的 Java 程序代码, 将主导权移交给应用程序, 初始化阶段就是执行类构造器方法的过程.

2.6 使用(Using)

到这里其实已经加载完成了!!!

3 示例

class A {
    public A() {
        System.out.println("A 构造方法");
    }

    static {
        System.out.println("A static");
    }
}
class B extends A {
    public B() {
        System.out.println("B 构造方法");
    }
    
    static {
        System.out.println("B static");
    }
}

public class TestJVM{
    public static void main(String[] args) {
        B b = new B();
    }
}

运行结果:
JVM 系列(二) --- JVM 类加载的基本流程_第2张图片
代码解读:
当 new B() 的时候,就先尝试加载 B 这个类. 然后加载 B 的时候发现 B 继承自 A, 于是又得先去加载 A, 两个类都加载完了再进行实例化操作, 构造方法是 new 对象的时候才调用, 此时已经初始化完成了; 口诀: 由父及子, 静态先行.

4 双亲委派模型

4.1 概述

  • 站在 Java 虚拟机的角度来看, 只存在两种不同的类加载器,: 一种是启动类加载器,这个类加载器由 C++ 来实现, 是虚拟机自身的一部分; 另外一种就是其他所有的类加载器, 这些类加载器都是由 Java 语言实现的, 独立存在于虚拟机外部, 并且全都继承自抽象类 java.lang.ClassLoader. 站在我们程序猿的角度, 类加载器就应该划分的更细致一些,自 JDK1.2以来, Java 一直保持这三层类加载器以及双亲委派的类加载器. 那么什么是双亲委派模型呢?
  • 如果一个类加载器收到了类加载的请求, 它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成, 每一个层次的类加载器都是如此, 因为所有的加载请求最终都应该传送到最顶层的启动类加载器中, 只有当父加载器反馈自己无法完成这个加载请求时,也就是在搜索范围内没有找到所需的类时,子加载器才会尝试自己去完成加载.
  • 在加载(Loading) 阶段, JVM 去哪个目录中来找 .class 这样的一个细节属于整个类加载过程中非常不起眼的一个环节, 但是这个环节却比较重要, 这是比较难以置信的, 可能是双亲委派模型这个名字比较霸气吧. 进行类加载过程中,一个比较重要的环节就是根据这个类的名字"java.lang.String"找到对应的 .class 文件, 在 JVM 中,有三个类加载器来负责进行这里找文件的操作, 这三个类加载器各有自己负责的区域.

4.2 三种类加载器

JVM 系列(二) --- JVM 类加载的基本流程_第3张图片

4.3 三种类加载器执行说明

执行步骤说明:

  • 这三个类加载器之间存在父子关系, 但是并不是继承里面的父类子类这样的关系, 而是像链表一样, 每个类里面都有一个 parent 字段, 指向了父类加载器.
  • 当在代码中使用到某个类的时候, 就会触发类加载器, 也就是说先从 AppClassLoader 开始, 但是 AppClassLoader 并不会真的开始去扫描自己负责的目录, 而是先去找其父类 ExtClassLoader, 但是 ExtClassLoader 也不会立即去扫描自己负责的目录, 而是也去找其父类加载器 BootStrap.
  • 到达 BootStrap 后, 也不会立即去扫描自己负责的目录, 也是先去找其父类加载器, 但是呢 BootStrap 上面已经没有了父类, 因此就去扫描自己负责的目录.
  • 如果在 BootStrap 中找到了需要的类, 就进行加载, 就没有其他类加载器的事情了, 但是如果没有匹配到合适的类, 就会告诉子加载器 ExtClassLoader, 再从 ExtClassLoader 所负责的目录中查找所需的类, 如果找到就加载, 如果未找到就去 AppClassLoader 所负责的目录区域查找类, 如果找到了就加载, 如果到这里还没有找到的话, 那就芭比Q了, 直接抛出 ClassNotFoundExcept 异常.

4.4 双亲委派模型的优点

1) 避免重复加载类: 例如 A 类和 B 类都有一个父类 C 类, 那么当启动 A 的时候就会将 C 类加载起来, 那么在 B 类进行加载时就不需要再重复加载 C 类了.
2) 安全性而言: 使用双亲委派模型也可以保证 Java 的核心 API 不会被篡改, 如果没有使用这种模型, 而是每个类加载器加载自己的话就会出现一些问题, 例如在编写一个成为 java.lang.Object 类的时候, 程序运行起来后系统就会出现多个不同的 Object 类, 而有些 Object 类又是用户自己提供的, 因此安全性就得不到保证了.
其实我们自己写的类加载器就不一定非得遵守双亲委派模型, 自己写的类加载器就是为了告诉程序去一些目录中找 .class, 例如 Tomcat webapps 里面就有很多类, 这些就是由 Tomcat 内部自己实现的类加载器来完成的, Tomcat 就没有遵循双亲委派模型.

你可能感兴趣的:(Java,JVM,后端,java,后端,开发语言)