JDK类加载器剖析

0.前言

我之所以深入研究 Java 类加载器,是为了解决一个奇怪的问题。流行出版物,也就是人们所认为的 Java 世界的灯塔,充斥着关于这个主题的相互矛盾和过时的信息。这种矛盾引发了我的调查 — — 在 Java 类加载器的迷宫中寻求清晰的答案。

作为一名 Java 开发人员,您可能遇到过ClassNotFoundException一些NoClassDefFoundError神秘的消息,它们会暂时中断您的编码流程。旨在阐明这些问题的在线资源往往反而增加了混乱。

让我们一起深入研究,消除复杂性。以下是我们将要解释的全貌:
在这里插入图片描述

附言:
Java SE 平台 API、它们的实现类和由平台类加载器或其祖先定义的 JDK 特定运行时类。
一些常见的类加载器的例子:org.apache.catalina.loader.WebappClassLoader、org.springframework.boot.loader.LaunchedURLClassLoader。
在java字节码中,JVM使用人类可读的符号引用,例如“java/lang/System.out:Ljava/io/PrintStream;”来表示字段、方法或类。
在解析阶段,JVM 将这些符号引用替换为直接引用实际内存地址。
这意味着对于system.out.println方法调用,jvm将用system.out文件的实际内存位置替换符号引用。
new、getstatic、putstatic、invokestatic。

1.声明

在我们进一步了解类加载器的机制之前,有必要强调一个重要的细节:

没有“通用”的 Java 虚拟机设计。

JVM由 Oracle Corporation指定,概述了任何 JVM 应具有哪些组件和行为。但是,此规范并未规定实现这些组件的单一方法。因此,我们发现了多种独特的 JVM 实现 — 例如HotSpot/OpenJDK、Eclipse OpenJ9或相对炒作的GraalVM(基于 OpenJDK)。这些实现均遵循 JVM 规范,但可能存在各种差异,包括性能特征、垃圾收集策略以及(您可能猜到的)类加载细节。

要记住的另一点是:

Java 虚拟机是平台相关的。

Windows 操作系统的 JVM 与 Linux 机器的 JVM 并不相同。“但是等一下,”您可能会说,“我以为 Java 就是一次编写,随处运行 — 平台独立性!”绝对正确。但是,Java 的平台独立性并不意味着 JVM 也是平台独立的。事实恰恰相反。

大多数关于这个主题的文章在描述 Java 时都没有给出具体的版本,这实际上会导致误解,因为 JVM 会随着每个版本而发展和变化。现在是 2023 年夏天,Java 世界正在期待版本 21,但在它发布之前,我们将专注于 Java 20,依靠Oracle 的 JVM 规范本身和Oracle Java SE 文档以简化操作。

考虑到这一点,让我们重新开始对 Java ClassLoader 系统的探索。

2.从底层开始

简而言之,当您运行应用程序时,JVM 会将必要的类加载到内存中,验证字节码,分配必要的资源,最后通过将字节码转换为主机可以理解的机器语言指令来执行代码。

但是JVM 加载到底意味着什么?Java 程序由类和接口组成,以人类可读的 Java 代码编写。要在机器上运行此代码,需要将其转换为机器可理解的字节码。此字节码存储在.class文件中,JVM 可以读取和执行这些文件。

因此,当我们谈论“加载类”时,我们指的是在磁盘上查找适当的 .class 文件 、读取其内容并将其带入 JVM 的运行时环境的过程,JVM 的运行时环境是计算机内存中专用于运行应用程序的特定部分。

或者,如果你愿意的话,可以使用 Oracle 中“加载”的更正式的定义:

加载是指查找具有特定名称的类或接口的二进制形式的过程,可能通过动态计算来实现,但更典型的是通过检索 Java 编译器先前从源代码计算出的二进制表示形式,并从该二进制形式构造一个Class对象来表示该类或接口。

3.进一步解释

实际上,ClassLoader 系统的作用不仅仅是查找类 - 它还通过强制执行 Java 运行时的二进制结构和命名空间规则来确保 Java 应用程序的完整性和安全性。同时,它还提供了从各种来源加载类的灵活性 - 不仅是本地文件系统,还包括通过网络、数据库,甚至是动态生成的类。让我们深入研究一下,分解一下步骤。

3.1. 加载——初始阶段

当 ClassLoader 负责定位特定类时,该过程就开始了。这可以由 JVM 本身启动,也可以由代码中的命令触发。本质上,ClassLoader 的工作是获取完全限定的类名(如java.lang.String)并从磁盘上的位置检索相应的类文件(如String.class)到 JVM 的内存中。

加载子系统并不是一个单独的动作,而是一个层级接力。每个 ClassLoader(父类加载器和子类加载器)都协作运行,传递责任接力棒,直到加载正确的类。

指导此协调类加载过程的基本原则是:

  • 可见性:子 ClassLoader 可以看到其父级加载的类,但反之则不然,从而确保了封装性;
  • 唯一性:父类加载的类不会再被子类加载,提高效率;
  • 委托层次结构:应用程序类加载器将类加载请求向上传递给平台类加载器和引导类加载器。如果它们找不到该类,请求将沿委托链向下传递;

现在让我们深入了解每个 ClassLoader。

3.1.1引导类加载器

Bootstrap ClassLoader 是该家族中最老的成员,它负责加载JVM 所需的<JAVA_HOME>/jmods文件夹中的核心 Java 库(例如java.lang.、java.util.​​等)。查看该图可以发现,其他 ClassLoader 是用 Java 编写的( java.lang.ClassLoader的对象 ),这意味着它们也需要加载到 JVM 中 — 这也是 Bootstrap ClassLoader 承担的任务。

还值得注意的是,许多资源将 Bootstrap ClassLoader 描述为其余类加载器的“父类”。这表示逻辑继承而不是直接 Java 继承,因为 Bootstrap ClassLoader是用本机代码编写的。以下代码行可以轻松证实这一点:

jshell> System.out.println(java.lang.ClassLoader.class.getClassLoader());
null

Bootstrap ClassLoader 也是Oracle 规范中唯一明确描述的ClassLoader 。其余的定义称为“用户定义”,由特定的 VM 供应商自行决定。

3.1.2.平台类加载器

在我看来,是最有争议的。

Java SE 20 文档说明如下:

平台类加载器负责加载平台类。平台类包括 Java SE 平台 API、其实现类以及由平台类加载器或其祖先定义的 JDK特定的运行时类。平台类加载器可用作实例的父类ClassLoader。

但是平台类和Bootstrap ClassLoader 加载的核心类有什么区别呢?让我们尝试观察它本质上加载了什么:

jshell> ClassLoader.getPlatformClassLoader().getDefinedPackages();
$1 ==> Package[0] { } // empty

事实证明,在一个完全空的 Java 程序中 — 什么都没有!现在,让我们尝试显式使用某个标准包中的类:

jshell> java.sql.Connection.class.getClassLoader()
$2 ==> jdk.internal.loader.ClassLoaders$PlatformClassLoader@27fa135a

jshell> ClassLoader.getPlatformClassLoader().getDefinedPackages()
$3 ==> Package[1] { package java.sql }

简单地说,Bootstrap 加载启动 JVM所需的核心运行时类,而平台加载开发人员可能需要的系统模块的公共类型。

在此背景下,值得一提的是,许多来源(例如Wikipedia、Baeldung)经常将平台类加载器称为扩展类加载器。然而,这并不完全准确。更正确的说法是,平台类加载器已经取代了Java 8 及更早版本中使用的扩展类加载器。这一变化伴随着模块系统 (JEP-261)的引入而来:

扩展类加载器不再是 的实例URLClassLoader,而是内部类的实例。它不再通过扩展机制加载类,该机制已被JEP 220删除。但是,它确实定义了选定的 Java SE 和 JDK 模块,有关详细信息,请参见下文。在其新角色中,此加载器称为平台类加载器,可通过新ClassLoader::getPlatformClassLoader 方法使用,并且它将是 Java SE 平台 API 规范所必需的。

3.1.3.应用类加载器

应用程序类加载器(也称为系统类加载器)可以说是日常 Java 开发环境中最常见的加载器。在 Java SE 20 中,它仍然保留了其传统的角色和功能。

此类加载器负责从已设置的类路径加载所有类。这些类可能来自目录、JAR 文件或类路径中指定的其他来源。Java 应用程序启动时,大多数用户定义的代码都在此处加载。

public class MediumTeller {

    public static void main(String[] args) {
        // jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7
        System.out.print(MediumTeller.class.getClassLoader());
    }
}

从类加载器层次结构的角度来看,应用程序类加载器是平台类加载器的逻辑子类加载器。这意味着,当加载某个类时,如果应用程序类加载器找不到该类,则请求将向上委托给平台类加载器,如果需要,则进一步委托给引导程序,以确保委托机制。

除了我们讨论过的三个主要类加载器之外,您还可以直接在代码中创建自己的用户定义类加载器。此功能提供了一种确保应用程序独立性的途径,由类加载器委托模型实现。Tomcat 等 Web 应用程序服务器利用这种方法来确保不同的 Web 应用程序和企业解决方案可以独立运行,即使它们托管在同一台服务器上。我们不会关注这一点,因为关于自定义创建主题已经有足够多的指南了。

值得一提的是,每个类加载器都维护自己的命名空间,用于记录已加载的类。当类加载器负责加载类时,它首先会查阅此命名空间,搜索完全限定类名 (FQCN) 以确定该类是否已加载。有趣的是,即使一个类与另一个类共享相同的 FQCN,如果它们存在于不同的命名空间中,它们仍被视为不同的类。如果类位于不同的命名空间中,则意味着它是由不同的类加载器加载的,从而增强了应用程序不同部分之间的自主性和分离性。

加载阶段的结果是 JVM 中类或接口类型的二进制表示。但是,此时类尚未准备好使用。

3.2.链接——填充空隙

链接阶段涉及几个复杂的步骤,以确保程序顺利执行。此阶段将加载的类或接口作为输入,并执行基本任务以验证代码的完整性、准备执行代码并解决其可能存在的任何依赖关系。
一旦类被加载,它就会s通过一个称为“链接”的阶段。此阶段涉及一系列步骤:

3.2.1.确认

此阶段对于维护 Java 运行时环境的稳健性至关重要。它检查类或接口的字节码以确保其结构正确性、与 JVM 的兼容性以及验证它是由合法的编译器生成的。

在 Java 程序可以通过网络传输并可能由恶意编译器生成的世界中,此过程变得至关重要。它检查符号表中的一致性、最终方法或类是否被不正确地覆盖、访问控制关键字的正确性、参数的准确数量和类型、正确的堆栈操作等等。

最后,如果验证检测到任何异常,它会抛出一个java.lang.VerifyError,导致一个java.lang.LinkageError。

3.2.2.准备

在此步骤中,JVM为类或接口的静态变量分配内存,并使用其默认值初始化它们。

此阶段不执行任何用户定义的初始化代码。

如果类或接口有实例字段,则在处理整个类层次结构的静态字段后,也会为这些字段分配内存并赋予默认值。此准备步骤为程序的执行奠定了基础,从而实现了高效的运行时性能。

由于链接涉及新数据结构的分配,因此可能会失败OutOfMemoryError。

3.2.3.决议

在这里,类或接口中的任何符号引用(指向其他类或接口的逻辑引用)都被替换为其实际的内存位置。这种从符号引用到直接引用的转换(通常称为动态链接)可确保类或接口的所有依赖项在运行时可用。

有趣的是,此步骤可以“懒惰”地执行,即仅当执行带有符号引用的语句时才执行。大多数 JVM 都使用这种方法,它可以节省资源,因为它可以防止不必要地加载可能永远不会调用的类或接口。

如果无法找到符号引用所指的类,则会引发java.lang.ClassDefNotFound或异常。java.lang.ClassNotFound。

3.3.初始化

在这里会执行每个被加载的类或接口的初始化逻辑(例如调用某个类的构造函数),由于JVM是多线程的,所以类或接口的初始化必须进行同步,防止被多个线程同时初始化,保证线程安全。

JVM 调用特殊方法(静态块和变量赋值的字节码版本),将所有静态变量设置为其指定的初始值。此时,该类终于可以使用了。

就这样:应用程序类已被找到、链接、初始化,现在可以集成到JVM中。JVM 现在退居幕后,将舞台留给您的应用程序。这些类充满了功能,并以错综复杂的网络相互连接,准备为您的应用程序注入活力。现在可以创建和操作类,并调用方法并设置由应用程序逻辑定义的变量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/517273.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

音视频开发之旅(81)- 图片视频“黑边”检测与去除

目录 1.“黑边“的场景 2. 二值化--单一颜色边缘的图像 3. canny边缘检测霍夫直线变换--处理负责的边缘图像 4. 性能优化 5. 资料 在页面展示中&#xff0c;如果图片/视频有黑边&#xff0c;比较影响体验&#xff0c;我我们今天实现下对图片/视频进行黑边检测。检测到黑边…

校招说明书

3400字的详细说明&#xff0c;介绍了程序员类岗位校招的整体时间节点和招聘流程。还对一些常见的问题进行讨论&#xff0c;例如内推、offer和三方、实习等。 第一章介绍基本的术语&#xff0c;第二章介绍整个校招的重要流程及时间点&#xff0c;然后第三章介绍每次招聘要经过的…

网络:HTTP协议

目录 序列化与反序列化 守护进程 网络计算器的实现 HTTP协议 http的代码演示 HTTPS 初步理解三次握手&#xff0c;四次挥手 ①tcp是面向连接的通信协议&#xff0c;在通信之前&#xff0c;需要进行3次握手&#xff0c;来进行连接的建立(谁connect谁握手) ②当tcp在断开…

《软件方法》剖析“提灯定损”,金将军和南丁格尔

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 这几天&#xff0c;江西出了“提灯定损”事件&#xff0c;成为“周公子”、“指鼠为鸭”之后的又一个大IP。 我也蹭一下这个IP&#xff0c;谈一谈软件开发中的业务建模和需求。 图1 “…

git上传到本地仓库

摘要&#xff1a;本地初始化init仓库&#xff0c;进行pull和push&#xff1b;好处是便于利用存储设备进行git备份 git init --bare test.git 随便到一个空的目录下git clone 然后使用git上传 把git仓库删除之后再clone一次验证一下是否上传成功&#xff1a; 如果在ubantu上面没…

实用技巧:如何取消app的截屏禁用

因为我想要在小鹅通App做笔记,但是被小鹅通App禁用截屏了,这真是一个很糟糕的使用体验,虽然可能是为了保护商家权益…… 方法1 可以让商家设置课程可以截屏 方法2 手机root,安装Xposed框架,利用Xposed框架上面的插件我们可以对手机进行高度定制化,而安装Xposed框架的…

Emacs之实现复制当前已打开文件buffer(一百三十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【嵌入式智能产品开发实战】(十六)—— 政安晨:通过ARM-Linux掌握基本技能【Linux shell揭秘】

目录 简述 开始 深入探究Linux内核 1.系统内存管理 2.软件程序管理 3.硬件设备管理 4.文件系统管理 GNU实用工具 1.核心GNU实用工具 2.shell Linux桌面环境 1.X Window软件 2.KDE Plasma桌面 3.GNOME桌面 4.其他桌面 Linux发行版 核心Linux发行版 特定用途的…

卷积篇 | YOLOv8改进之引入全维度动态卷积ODConv | 即插即用

前言:Hello大家好,我是小哥谈。ODConv是一种关注了空域、输入通道、输出通道等维度上的动态性的卷积方法,一定程度上讲,ODConv可以视作CondConv的延续,将CondConv中一个维度上的动态特性进行了扩展,同时了考虑了空域、输入通道、输出通道等维度上的动态性,故称之为全维度…

在一套Dockerfile中完成编译和运行环境部署

大纲 解释型语言编译环境解释环境编译型语言编译环境运行环境 方法编译环境安装系统安装编译依赖下载代码特殊处理&#xff08;可以忽略&#xff09;编译准备&#xff08;可以忽略&#xff09;编译打包依赖&#xff08;编译结果&#xff09; 运行环境安装操作系统安装运行时依赖…

RabbitMQ高级-应用问题、集群搭建

1.消息补偿 消息可靠性保障&#xff1a;——消息补偿机制 需求&#xff1a;100%确保消息发送成功 2.幂等性保障 幂等性指一次和多次请求某一资源&#xff0c;对于资源本身应该具有同样的结果。也就是说&#xff0c;其任意多次执行对资源本身所产生的影响均与第一次执行的影响…

AI编程005/ 逆向生成mysql的建表语句

1/ 通过insert into 语句生成建表语句 有些时候我们能获取到表的insert语句&#xff0c;但是没有表结构。我们可以借助AI工具&#xff0c;让其逆向生成mysql的建表语句。 提示词如下&#xff1a; 根据下面的SQL语句&#xff0c;逆向生存mysql的建表语句&#xff0c;每个字段…

Redis配置与优化

目录 引言 一、关系型数据库与非关系型数据库 1、关系型数据库 2、非关系型数据库 3、关系型数据库和非关系型数据库的区别 1.数据存储方式不同 2.扩展方式不同 3.对事物性的支持不同 4、非关系型数据库产生背景 二、Redis简介 1、Redis优点 2、Redis为什么这麽快&…

企业员工岗前培训管理系统的设计与实现(论文+源码)_kaic

摘 要 有效的处理想要的相关信息和如何传播有效的信息&#xff0c;一直是人类不断探索的动力。人类文明火种的传承都是通过了多种媒介作为载体&#xff0c;也是随着社会生产力的发展不断的更新。随着互联网的到来&#xff0c;信息传播与管理都上升了一个新的台阶&#xff0c;并…

基于单片机的全自动洗衣机系统仿真设计

**单片机设计介绍&#xff0c;基于单片机的全自动洗衣机系统仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的全自动洗衣机系统仿真设计概要是关于利用单片机技术实现全自动洗衣机控制功能的系统设计概述。以…

【5】JavaScript - 控制语句 循环[ for/while ]

控制语句 while/do...while 语句while 语句&#xff1a;do...while 语句&#xff1a;两者的区别无限循环 for 语句普通的 for 循环for...in 循环for...of 循环 循环过程&#xff1a;跳出/跳过跳出跳过 循环嵌套扩展延申&#xff1a;做一次杠精 当前 控制语句 章节主要介绍 循环…

【QT+QGIS跨平台编译】056:【pdal_kazhdan+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、pdal_kazhdan介绍二、pdal下载三、文件分析四、pro文件五、编译实践一、pdal_kazhdan介绍 pdal_kazhdan 是 PDAL(Point Data Abstraction Library)相关的 Kazhdan 算法的实现。PDAL 是一个用于处理和分析点云数据的开源库,而 Kazhdan 算法通常…

【教学类-09-09】20240406细线迷宫图05(正方形)30格+动物+箭头(15CM横版一页-1份横版)

作品展示&#xff1a; 背景需求&#xff1a; 增加迷宫图的吸引力&#xff0c;起点的地方放一个小动物。 素材准备&#xff1a; 图片来自midjounery文生图&#xff08;四图&#xff09;&#xff0c;但同种动物只留1个&#xff08;如4个老鼠只保留一只老鼠&#xff09;&#xff…

YOLOv8 UI界面设计+热力图显示

进入可视化设计界面&#xff0c;设计UI pyside6-designer 设计好UI保存&#xff0c;然后通过以下命令将ui文件保存为py pyside6-uic myui.ui > myui.py 通过以下命令将资源文件qrc保存为py pyside6-rcc my_rc.qrc > my_rc.py 写主窗口函数实现功能... 项目基于yol…

React - 连连看小游戏

简介 小时候经常玩连连看小游戏。在游戏中&#xff0c;当找到2个相同的元素就可以消除元素。 本文会借助react实现连连看小游戏。 实现效果 实现难点 1.item 生成 1. 每一个图片都是一个item&#xff0c;items数组的大小为size*size。 item对象包括grid布局的位置&#xff0c;…