一、什么是类加载器,类加载器有哪些?

要想理解类加载器的话,务必要先清楚对于一个Java文件,它从编译到执行的整个过程。

jvm.png

  • 类加载器:用于装载字节码文件(.class文件)

  • 运行时数据区:用于分配存储空间

  • 执行引擎:执行字节码文件或本地方法

  • 垃圾回收器:用于对JVM中的垃圾内容进行回收

1.类加载器

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

2.类加载器种类

类加载器根据各自加载范围的不同,划分为四种类加载器:

  • 启动类加载器(BootStrap ClassLoader):

    该类并不继承ClassLoader类,其是由C++编写实现。用于加载JAVA_HOME/jre/lib目录下的类库。

  • 扩展类加载器(ExtClassLoader):

    该类是ClassLoader的子类,主要加载JAVA_HOME/jre/lib/ext目录中的类库。

  • 应用类加载器(AppClassLoader):

    该类是ClassLoader的子类,主要用于加载classPath下的类,也就是加载开发者自己编写的Java类。

  • 自定义类加载器:

    开发者自定义类继承ClassLoader,实现自定义类加载规则。

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

类加载器的体系并不是“继承”体系,而是委派体系,类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。

3.类加载的时机

什么时候会发生类初始化:

  • 类的主动引用一定会发生类初始化)

    • 当虚拟机启动,先初始化mian方法所在的类

    • new一个类的对象

    • 调用类的静态成员(不包括final常量)和静态方法

    • 使用java.lang.reflect包的方法对类进行反射调用

    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则先初始化其父类

  • 类的被动引用不会发生类初始化)

    • 访问类的静态常量(被 final 修饰的字段,且在编译时可以确定的常量)不会导致类的初始化。

    • 当访问一个静态域时,只有正真声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化

    • 通过数组定义类引用,不会发生类初始化

    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

二、双亲委派模型

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

三、JVM为什么采用双亲委派机制

(1)通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。

(2)为了安全,保证类库API不会被修改

在工程中新建java.lang包,接着在该包下新建String类,并定义main函数

 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库。

四、类装载的执行过程

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

类加载过程详解

1.加载

  • 通过类的全名,获取类的二进制数据流

  • 解析类的二进制数据流为方法区内的数据结构(Java类模型)

  • 创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据访问入口

2.验证

image-20230506101420202.png(1)文件格式验证:

  • 是否符合Class文件的规范

(2)元数据验证

  • 这个类是否有父类(除了Object这个类之外,其余的类都应该有父类)

  • 这个类是否继承(extends)了被final修饰过的类(被final修饰过的类表示类不能被继承)

  • 类中的字段、方法是否与父类产生矛盾。(被final修饰过的方法或字段是不能覆盖的)

(3)字节码验证

  • 主要的目的是通过对数据流和控制流的分析,确定程序语义是合法的、符合逻辑的。

(4)符号引用验证:

  • 符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量

比如:int i = 3; 字面量:3 符号引用:i

3.准备

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

  • static变量,分配空间在准备阶段完成(设置默认值),赋值初始化阶段完成

  • static变量是final基本类型,以及字符串常量值已确定赋值准备阶段完成

  • static变量是final的引用类型,那么赋值也会在初始化阶段完成

4.解析

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

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

5.初始化

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

  • 如果初始化一个类的时候,其父类未初始化,则优先初始化其父类。

  • 如果同时包含多个静态变量静态代码块,则按照自上而下的顺序依次执行。

在 Java 中对类变量进行初始值设定有两种方式:

1:声明类变量是指定初始值

2:使用静态代码块为类变量指定初始值

JVM 初始化步骤

1、假如这个类还没有被加载和连接,则程序先加载并连接该类

2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

3、假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

1:创建类的实例,也就是 new 的方式

2:访问某个类或接口的静态变量,或者对该静态变量赋值

3:调用类的静态方法

4:反射(如 Class.forName(“com.shengsiyuan.Test”)

5:初始化某个类的子类,则其父类也会被初始化

6:Java 虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe 命令来运行某个主类

47:什么是类加载器?

6.使用

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

  • 调用静态类成员信息(比如:静态字段、静态方法)

  • 使用new关键字为其创建对象实例

7.卸载

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