首页 > 其他分享 >【JVM第2课】类加载子系统(类加载器、双亲委派)

【JVM第2课】类加载子系统(类加载器、双亲委派)

时间:2024-10-30 09:50:01浏览次数:5  
标签:初始化 ExtClassLoader AppClassLoader 静态 JVM 子系统 加载

类加载系统加载类时分为三个步骤,加载、链接、初始化,下面展开介绍。

文章目录

无痛快速学习JVM,欢迎订阅本免费专栏

类加载子系统结构图:
在这里插入图片描述

1 类加载器

JVM 使用类加载器加载 class 文件,类加载器可分为引导类加载器自定义类加载器两种。

引导类加载器(Bootstrap ClassLoader),有时也被称作启动类加载器或者零类加载器(Null ClassLoader),是 Java 虚拟机中最基础的类加载器之一。它的主要职责是加载 Java 核心类库。

自定义类加载器需要继承自 ClassLoader 类,JDK 默认提供了一些。比较重要的有两个,拓展类加载器(ExtClassLoader)应用类加载器(AppClassLoader)

下面展开说说这三个加载器的作用、区别以及联系。先看一张图:

在这里插入图片描述

1.1 引导类加载器(BootStrapClassLoader)

特点:

  1. 内部实现:引导类加载器并不是通过 Java 代码实现的,而是用 C++ 或者其他本地语言编写的,并且是 JVM 的一部分。
  2. 加载路径:引导类加载器通常从 $JAVA_HOME/jre/lib/ 或类似的位置加载 Java 核心类。
  3. 无父类加载器:引导类加载器没有显式的父类加载器,这是因为它的设计目的是为了加载 Java 最基础的类库,而这些类库是任何其他类加载器工作的前提。因此,它不需要依赖于任何其他类加载器。
  4. 不可见性:引导类加载器并不是对所有 Java 应用程序都可见的,因为它是 JVM 的一部分,而不是标准的 Java 类加载器层次结构的一部分。
  5. 优先级:引导类加载器通常是整个类加载过程的第一步,当 Java 应用程序启动时,它会首先加载必要的核心类库,然后才允许后续的类加载器(如扩展类加载器和应用类加载器)开始工作。

1.2 拓展类加载器(ExtClassLoader

特点:

  1. 内部实现ExtClassLoader是在sun.misc.Launcher类里的静态内部类,继承自 ClassLoader 类,重写 loadClass() 方法。
  2. 加载路径ExtClassLoader 主要负责加载位于 $JAVA_HOME/jre/lib/ext 目录下的扩展类库。
  3. 委托模型ExtClassLoader 遵循 Java 类加载器的委托模型。当它收到一个类加载请求时,它首先会尝试使用其父类加载器(即 Bootstrap ClassLoader)来加载这个类。如果父类加载器无法加载,则 ExtClassLoader 会尝试自己加载。
  4. 优先级ExtClassLoader 位于 BootstrapClassLoader 之后,但在 AppClassLoader之前。这意味着它继承了 Bootstrap ClassLoader 的特性,并且它加载的类对 Application ClassLoader 可见。

1.3 应用类加载器(AppClassLoader)

特点:

  1. 内部实现AppClassLoader也是在sun.misc.Launcher类里的静态内部类,继承自 ClassLoader 类,重写 loadClass() 方法。
  2. 加载路径AppClassLoader主要负责的目录是当前应用程序的 classpath 所指定的路径,也就是说我们自己写的类默认都是通过AppClassLoader加载的。我们在IDEA里运行代码时,仔细观察控制台可以发现第一行通过-classpath指定了当前应用程序的class文件的目录
  3. 委托模型AppClassLoader 遵循 Java 类加载器的委托模型。当它收到一个类加载请求时,它首先会尝试使用其父类加载器 ExtClassLoader来加载这个类。如果父类加载器无法加载,则 AppClassLoader 会尝试自己加载。
  4. 优先级AppClassLoader位于ExtClassLoader 之后。

除了这些特点外,AppClassLoader还有一些别的用途:

  1. 加载第三方 Jar 包:当应用程序依赖于第三方库时,这些库通常会被打包成 JAR 包,并放置在类路径中。AppClassLoader 会加载这些 JAR 包中的类。
  2. 动态加载类:在一些需要动态加载类的场景中,如 Spring Boot 应用程序,AppClassLoader 可以用于动态加载和卸载类。

1.4 双亲委派

原因一:前面我们介绍了引导类加载器、拓展类加载器、应用类加载器分别负责不同的路径下的class文件,但是并不是完全不相交的,比如-classpath除了指定当前应用程序的class文件目录外,也会指定$JAVA_HOME/jre/lib/目录下的某些 jar 包,所以要避免重复加载某些类。

原因二:如果我们的程序被黑客攻击了,比如黑客自己创建了一个java.lang的包,里面创建了一个名为String的类,把这个包和类植入我们正在运行的项目里,如果他的这个类被加载了,那我们项目里的String就会被篡改。

为了避免以上两种原因,我们要保证类只加载一次,并且保证越靠近 JVM 的类加载器优先级越高。这就是双亲委派干的事情!!!

原理:

引导类加载器、拓展类加载器、应用类加载器这三者之前有个关系,但又不是父子类关系,而是应用类加载器有个parent属性是拓展类加载器的对象。拓展类加载器的parent为空,所以会调用引导类加载器。我们观察ClassLoaderloaderClass()方法可以得出类的加载过程:

loadClass()方法

双亲委派过程

简单来说就是,

通过AppClassLoader加载class时会先用ExtClassLoader去加载这个类;

通过ExtClassLoader加载class时会先用BootStrapClassLoader去加载这个类;

好处:

  • 避免类被重复加载。
  • 防止JVM核心类被篡改。

2 链接

class 加载完后会进行链接,分为三步:验证、准备、解析。

2.1 验证

第一步是验证 class 文件是否正确,比如验证格式。

2.2 准备

对 static 修饰的属性赋予一个默认值,但这一步不会赋初始值。

举个例子,class 里定义了一个static int a = 1,准备阶段会把 a 赋值为 0,在初始化阶段 a 才会 = 1。

2.3 解析

将符号引用解析为直接引用。什么意思呢?

首先我们需要知道类被加载后是放到方法区的,每个类都是一个 Klass 对象(也可称为 Klass 结构)。

一般情况下我们都会在一个A类里使用到别的B类,使用方式是B类的全限定名,就只是一个字符串。但是JVM 实际在执行的时候需要从方法区中找到B类的 Klass 对象,解析的作用就是把这个名称字符串替换为实际的 Klass 对象内存地址。“符号引用”就是名称字符串、“直接引用”就是 Klass 对象内存地址。

3 初始化

初始化是类加载子系统的最后一个阶段,也是最为关键的阶段之一。下面详细介绍初始化阶段的内容及其重要性。

3.1 定义

初始化阶段是类加载过程中的最后一个阶段,它负责执行类构造器 <clinit> 方法,并初始化类的静态变量。

3.2 主要任务

初始化阶段的主要任务包括:

  • 执行类构造器 <clinit> 方法<clinit> 方法是一个特殊的静态构造器,它负责对类进行初始化。每个类都有一个 <clinit> 方法,该方法在类第一次被初始化时由 JVM 自动生成并执行。
  • 初始化类变量:类中的静态变量(即类变量)在 <clinit> 方法中被赋值。

3.3 <clinit> 方法的特点

  • 静态块:在类定义中,静态代码块会被编译器转化为 <clinit> 方法中的语句。
  • 顺序执行:如果一个类有多个静态代码块,它们将按照在源代码中出现的顺序依次执行。
  • 线程安全<clinit> 方法是线程安全的,这意味着即使有多个线程同时初始化同一个类,也不会发生冲突。

3.4 示例代码

下面是一个简单的示例,展示类的初始化过程:

public class InitializationExample {
    static {
        System.out.println("执行静态初始化块。");
    }

    static int staticVar = initializeStaticVar();

    private static int initializeStaticVar() {
        System.out.println("初始化静态变量。");
        return 10;
    }

    public static void main(String[] args) {
        System.out.println("静态变量初始化为: " + staticVar);
    }
}

输出如下:

执行静态初始化块。
初始化静态变量。
静态变量初始化为: 10

3.5 初始化时机

类的初始化通常在以下几种情况下触发:

  • 首次创建类的实例:当第一次创建类的实例时,JVM 会初始化该类。
  • 调用类的静态方法:当第一次调用类的静态方法时,JVM 会初始化该类。
  • 引用类的静态字段:当第一次引用类的静态字段时,JVM 会初始化该类。
  • 反射性引用:当通过 java.lang.Classjava.lang.reflect 包中的方法来引用类时,如果这些方法会导致类的初始化,那么 JVM 会初始化该类。
  • 初始化子类时:当初始化一个类的子类时,如果父类还没有被初始化,那么 JVM 会首先初始化父类。

3.6 初始化顺序

类的初始化顺序遵循一定的规则:

  • 如果类 A 引用了类 B 的静态字段或调用了类 B 的静态方法,那么类 B 必须先于类 A 被初始化。
  • 如果类 A 继承自类 B,那么类 B 必须先于类 A 被初始化。

标签:初始化,ExtClassLoader,AppClassLoader,静态,JVM,子系统,加载
From: https://blog.csdn.net/weixin_42892177/article/details/143323405

相关文章

  • 【JVM第3课】运行时数据区
    运行时数据区结构图如下:可分为5个区域,分别是方法区、堆区、虚拟机栈、本地方法栈、程序计数器。这里大概介绍一下各个模块的作用,会在后面的文章展开讲。类加载子系统会把类信息加载到方法区,程序运行时会创建线程,每个线程都有自己的虚拟机栈、本地方法栈、程序计数器,线程每执......
  • 【JVM第4课】程序计数器
    Java程序计数器(ProgramCounterRegister)是Java虚拟机(JVM)运行数据区的一个组成部分。每个线程都有它自己的程序计数器,这部分内存用于存储该线程下一条要执行的字节码指令的地址。如果该方法为本地方法(NativeMethod),则程序计数器的值为未定义。特点:线程私有:每个线程都拥有独立的......
  • 3D Gaussian Splatting代码详解(一):模型训练、数据加载
    1模型训练deftraining(dataset,opt,pipe,testing_iterations,saving_iterations,checkpoint_iterations,checkpoint,debug_from):first_iter=0#初始化高斯模型,用于表示场景中的每个点的3D高斯分布gaussians=GaussianModel(dataset.sh_degree)......
  • 2 类加载子系统(类加载器、双亲委派)
    类加载系统加载类时分为三个步骤,加载、链接、初始化,下面展开介绍。类加载子系统结构图:1类加载器JVM使用类加载器加载class文件,类加载器可分为引导类加载器和自定义类加载器两种。引导类加载器(BootstrapClassLoader),有时也被称作启动类加载器或者零类加载器(NullClassLoad......
  • C++之OpenCV入门到提高002:加载、修改、保存图像
    一、介绍今天是这个系列《C++之Opencv入门到提高》得第二篇文章。今天这个篇文章很简单,只是简单介绍如何使用Opencv加载图像、显示图像、修改图像和保存图像,先给大家一个最直观的感受。但是,不能认为很简单,只是让学习的过程没那么平滑一点,以后的路就好走了。OpenCV具......
  • JVM参数中X,XX,D的说明
    JVM参数遵循一定的命名和使用规律,这些参数可以根据其前缀分为几类,每类都有特定的用途和格式。下面是关于-X、-XX和-D开头的JVM参数的一些常见规律:-X 参数含义:非标准选项,通常用于设置内存大小和其他与性能相关的参数。示例:-Xms<size>:设置初始Java堆大小。-Xmx<......
  • 详解:类什么时候加载?
            在Java编程语言中,类的加载(ClassLoading)是Java虚拟机(JVM)将类的字节码从文件或其他来源(如网络)加载到JVM(Java虚拟机)内存中,并转化为运行时数据结构(如java.lang.Class对象)的过程。这个过程通常发生在程序运行时,但也可以在特定条件下提前发生。类的加载时机取决于多......
  • C语言和Groovy在JVM兼容性上的区别
    ##C语言和Groovy在JVM兼容性上的区别在探讨C语言和Groovy在JVM(Java虚拟机)兼容性上的差异时,核心观点可以概括为:C语言不直接兼容JVM、Groovy与JVM高度兼容。C语言是一种通用的、过程式的编程语言,它直接编译为机器语言,执行效率高,但它并不直接兼容JVM,这是因为JVM是为运行Java字节码......
  • Android15音频进阶之音频策略加载及使用(九十一)
    简介:CSDN博客专家、《Android系统多媒体进阶实战》一书作者新书发布:《Android系统多媒体进阶实战》......
  • JVM(方法区包含常量池及StringTable)
    方法区(此图省略了栈等结构,JVM结构详细图在JVM简介中,方法区中常量池应为运行时常量池)定义方法区(MethodArea)是Java虚拟机(JVM)的一部分,它与Java堆一样,是被JVM实例中所有线程共享的区域。方法区在JVM启动时创建,可以选择固定大小或允许动态扩展。这个区域的大小直接影响到系统能够......