JDK类加载器剖析

news2025/7/17 4:06:47

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 现在退居幕后,将舞台留给您的应用程序。这些类充满了功能,并以错综复杂的网络相互连接,准备为您的应用程序注入活力。现在可以创建和操作类,并调用方法并设置由应用程序逻辑定义的变量。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle 数据库中的全文搜索

Oracle 数据库中的全文搜索 0. 引言1. 整体流程2. 创建索引2-1. 创建一个简单的表2-2. 创建文本索引2-3. 查看创建的基础表 3. 运行查询3-1. 运行文本查询3-2. CONTAINS 运算符3-3. 混合查询3-4. OR 查询3-5. 通配符3-6. 短语搜索3-7. 模糊搜索&#xff08;Fuzzy searches&…

【Servlet】Servlet入门

文章目录 一、介绍二、入门案例导入servlet-api的解决办法 一、介绍 概念&#xff1a;server applet&#xff0c;即&#xff1a;运行在服务器端的小程序 Servlet就是一个接口&#xff0c;定义了Java类被浏览器访问到&#xff08;tomcat识别&#xff09;的规则。 将来我们定义…

java数据结构与算法刷题-----LeetCode504. 七进制数

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 倒退迭代&#xff08;除基取余法&#xff09;2. 省略掉反转操…

【JavaEE初阶系列】——文件操作 IO 之 文件系统操作

目录 &#x1f4dd;认识文件 &#x1f6a9;树型结构组织 和 目录 &#x1f388;绝对路径和相对路径 &#x1f6a9;文件类型 &#x1f4dd;文件系统操作 &#x1f388;File 概述 &#x1f388;File类的使用 1. 绝对路径 vs 相对路径 2. 路径分隔符 3. 静态成员变量 4…

小明记账簿-记账工具

今天不了技术&#xff0c;聊一下工具&#xff0c;最近耗费无数个日日夜夜&#xff0c;做了一个记账小程序&#xff0c;感觉很实用&#xff0c;简单方便&#xff0c;希望帮助那些需要帮助的人。 想轻松管理个人财务吗&#xff1f;试试小明记账簿吧&#xff01;它是一款便捷&…

篮桥杯刷题第n天(dp更新)

最长上升子序列 输入&#xff1a; 10 1 4 5 1 4 1 9 1 9 输出&#xff1a; 4 算法思想&#xff1a;记录每个数为结尾的最长子序列长度&#xff0c;作为dp数组。 eg&#xff1a;&#xff08;这个是以每个位置开头的记录最长的来穷举但核心仍为上述的算法思想&#xff09; …

vue快速入门(一)vue的导入方法

注释很详细&#xff0c;直接上代码 新增内容 下载js代码导入实例数据绑定显示 源码 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-widt…

【解读Kubernetes架构】全面指南,带你掌握Kubernetes的设计原理与构成!

了解 Kubernetes 架构&#xff1a;综合指南 前言一、什么是 Kubernetes 架构&#xff1f;1.1、控制平面1.2、工作节点 二、Kubernetes 控制平面组件2.1、kube-api服务器2.2、etcd2.3、kube-scheduler2.4、Kube 控制器管理器2.5、云控制器管理器 &#xff08;CCM&#xff09; 三…

【HarmonyOS】ArkUI - 动画

利用属性动画、显示动画、组件转场动画实现组件动画效果。 一、属性动画 属性动画是通过设置组件的 animation 属性来给组件添加动画&#xff0c;当组件的 width、height、Opacity、backgroundColor、scale、rotate、translate 等属性变更时&#xff0c;可以实现渐变过渡效果。…

有人用GPT来做日内交易,居然赚钱了!但是……

在我们还在烦恼会不会被AI替代时&#xff0c;已经有人在教ChatGPT去炒股票了。 在近年ChatGPT火速出圈后&#xff0c;围绕AI能取代什么职业的讨论持续受到大众关注。 从事客服、编程、法律合规以及内容创作等行业人员最早感受到这股AI带来的寒意。 那ChatGPT能不能替代交易员…

Web APIs简介 Dom

JS的组成 API API 是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于软件或硬件得以访问一组例程的能力&#xff0c;而又无需访问源码&#xff0c;或理解内部工作机制的细节 简单理解&#xff1a;API是给程序员提供的一种工具&#xff0c;以便能更轻松的实现…

[计算机知识] 各种小问题思考

哈希算法以及哈希冲突 哈希算法&#xff1a;将任何长度的输入通过散列函数转换成固定长度的字符串 哈希冲突&#xff1a;不同的输入经过哈希函数处理后得到相同的哈希值 因为哈希函数的输出域是有限的 解决哈希冲突&#xff1a; 1. 开放寻址&#xff1a;产生哈希冲突后&…

刷题之Leetcode704题(超级详细)

704. 二分查找 力扣题目链接(opens new window)https://leetcode.cn/problems/binary-search/ 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&am…

【Apache Doris】周FAQ集锦:第 1 期

【Apache Doris】周FAQ集锦&#xff1a;第 1 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户和…

【Java设计模式】序:设计模式总体概述

目录 什么是设计模式设计模式的分类1 创建型模式1.1. 单例&#xff08;Singleton&#xff09;1.2 原型&#xff08;Prototype&#xff09;1.3 工厂方法&#xff08;FactoryMethod&#xff09;1.4 抽象工厂&#xff08;AbstractFactory&#xff09;1.5 建造者&#xff08;Builde…

<网络安全>《71 微课堂<常见的国家级APT组织介绍>》

1 简介 国家级APT&#xff08;Advanced Persistent Threat&#xff0c;高级持续性威胁&#xff09;组织是有国家背景支持的顶尖黑客团伙&#xff0c;专注于针对特定目标进行长期的持续性网络攻击。 2 十大APT 2.1 NSA 美国国家安全局&#xff08;National Security Agency&a…

Redis面试题28道

1、什么是类加载器&#xff0c;类加载器有哪些&#xff1f; 1、什么是类加载器&#xff1f; 类加载器负责加载所有的类&#xff0c;其为所有被载入内存的类生成一个 java.lang.Class 实例对象。 2、类加载器有哪些&#xff1f; JVM 有三种类加载器&#xff1a; &#xff08…

基于GaN的半导体光学放大器SOA

摘要 基于GaN的材料可覆盖很宽的光谱范围&#xff0c;以紫外、紫、蓝、绿和红波发射的激光二极管已经商业化。基于GaN的半导体光学放大器&#xff08;SOA&#xff09;具有提高激光二极管输出功率的能力&#xff0c;因此SOA将有很多潜在应用。未来需要利用短波、超快脉冲特性的…

GD32F470_ADS1115 超小型 16位 模数转换器 ADC 4通道模块移植

2.9 ADS1115多路模数转换器 ADS1115 器件是兼容 IIC 的 16 位高精度低功耗模数转换器 (ADC)&#xff0c;采用超小型无引线 X2QFN-10 封装和 VSSOP-10 封装。ADS111x 器件采用了低漂移电压基准和振荡器。ADS1114 和 ADS1115 还采用可编程增益放大器(PGA)和数字比较器。这些特性加…

kali报错Unable to connect to remote host: No route to host,如何解决??

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…