深入JVM:类加载器和双亲委派模型

目录

  • [1. 什么是类加载器](#1. 什么是类加载器)
  • [2. 类加载器的类型](#2. 类加载器的类型)
  • [3. 双亲委派模型](#3. 双亲委派模型)
  • [4. 类装载的过程](#4. 类装载的过程)

1. 什么是类加载器

如果想要了解什么是类加载器就需要清楚一个Java文件是如何运行的。我们可以看下图:

首先要知道操作系统是不能直接运行Java文件的,所以就需要通过JVM将Java文件转换为操作系统可以运行的文件类型,步骤如下:

  • 类加载器把Java代码转换为字节码文件
  • 运行时数据区将字节码文件加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统来执行,而是需要有执行能力的执行引擎来运行
  • 执行引擎将字节码翻译为底层系统指令,再交给CPU来执行,而这个时候需要调用其它语言的本地库接口来实现整个程序的功能

JVM只会运行二进制文件,而类加载器 (ClassLoader)的主要作用就是将字节码 文件加载到JVM 中 ,从而让Java程序能够启动起来。现有的类加载器基本上都是 java.lang.ClassLoader的子类,该类的只要职责就是用于将指定的类找到或生成对应的字节码文件,同时类加载器还会负责加载程序所需要的资源

2. 类加载器的类型

类加载器根据各自的加载范围不同,进行了划分主要是四种:

  • 启动类加载器:这个类并不继承ClassLoader类,其中是由C++编写实现。用于加载JAVA_Home/jre/lib目录下的类库
  • 扩展类加载器:该列是ClassLoader的子类,主要是加载JAVA_HOME/jre/lib/ext目录中的类库
  • 应用类加载器:该类时ClassLoader的子类,主要是用于加载classPath下的类,也就是加载开发者自己编写的Java类
  • 自定义类加载器:开发者自定义类继承ClassLoader,实现自定义类加载规则

上述三种类加载器的层次结构如下如下:

而类加载器的体系不是继承关系的,而是委派体系,类加载器首先会到自己的父亲中查找类和资源,如果找不到才回到自己的本地进行查找。类加载器的委托行为动机是为了避免相同的类被多次加载

3. 双亲委派模型

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类, 而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以 完成类加载任务,就返回成功;只有父类加载器无法完成此加载任务时,才由下一级去加载

那么为什么会需要使用双亲委派模型呢?

  • 通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性
  • 为了安全,保证类库API不会被修改

这里解释一下如何保证类库的API不会被修改:当我们创建一个类String的时候,由于在Java中本身就存在String类,所以使用双亲委派模型的时候,在启动类加载器就会加载Java中的String类,而不会使用应用类加载器进行加载。

java 复制代码
public class String {
    public static void main(String[] args) {
        System.out.println("demo info")
    }
}

此时执行main函数,会出现异常,在类 java.lang.String 中找不到 main 方法

出现该信息是因为由双亲委派的机制,java.lang.String的在启动类加载器 (Bootstrap classLoader)得到加载,因为在核心jre库中有其相同名字的类文件, 但该类中并没有main方法。这样就能防止恶意篡改核心API库

4. 类装载的过程

类从加载到JVM中开始,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中,验证、准备和解析这三个部分统称为连接

加载

加载时主要的作用是以下三点:

  • 通过类的全名,获取类的二进制数据流
  • 解析类的二进制数据流为方法区内的数据结构(Java类模型)
  • 创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口

上面说的可能优点难以理解,以这个图片来讲解一下它的作用。就比如当前有一个Person进行类的加载,那么他主要是分为两个部分,第一部分将Person中定义的字段以及方法存储到元空间中,第二部分时将实例化的对象存储到堆中,而堆当中的实例化对象的对象头会指向当前的class对象,然后class对象指向元空间的数据结构

验证

验证类是否符合 JVM 规范,安全性检查

  • 文件格式验证:是否符合Class文件的规范
  • 元数据验证
    • 这个类是否有父类(除了Object这个类之外,其余的类都应该有父类)
    • 这个类是否继承(extends)了被final修饰过的类(被final修饰过的类表示类不能被继承)
    • 类中的字段、方法是否与父类产生矛盾。(被final修饰过的方法或字段是不能覆盖的)
  • 字节码验证
    • 主要的目的是通过对数据流和控制流的分析,确定程序语义是合法的、符合逻 辑的
  • 符号引用验证:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量

准备

为类变量分配内存并设置类变量初始值

  • static变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成
  • static变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成
  • static变量是final的引用类型,那么赋值也会在初始化阶段完成
java 复制代码
public class Application {
    static int b = 10;
    static final int c= 20,
    static final String d = "hello";
    static final Object obj= new Object();
}

解析

把类中的符号引用转换为直接引用

比如:方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法

初始化

对类的静态变量,静态代码块执行初始化操作

  • 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类
  • 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行

使用

JVM 开始从入口方法开始执行用户的程序代码

  • 调用静态类成员信息(比如:静态字段、静态方法)
  • 使用new关键字为其创建对象实例

卸载

当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存

相关推荐
东阳马生架构3 小时前
JVM实战—1.Java代码的运行原理
jvm
ThisIsClark5 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
王佑辉5 小时前
【jvm】内存泄漏与内存溢出的区别
jvm
大G哥8 小时前
深入理解.NET内存回收机制
jvm·.net
泰勒今天不想展开8 小时前
jvm接入prometheus监控
jvm·windows·prometheus
东阳马生架构1 天前
JVM简介—3.JVM的执行子系统
jvm
程序员志哥1 天前
JVM系列(十三) -常用调优工具介绍
jvm
后台技术汇1 天前
JavaAgent技术应用和原理:JVM持久化监控
jvm
程序员志哥1 天前
JVM系列(十二) -常用调优命令汇总
jvm