JVM——Java 虚拟机是如何加载 Java 类的?

引入

在 Java 世界的底层运作中,类加载机制扮演着一个既神秘又关键的角色。它就像是一个精心设计的舞台幕后 machinery,确保了 Java 程序能够顺利运行。今天,我们就深入探索 Java 虚拟机(JVM)是如何加载 Java 类的。

类加载的背景

Java 语言的一个核心优势是它的平台无关性,而这一优势在很大程度上依赖于 Java 虚拟机(JVM)。JVM 作为一个抽象的规范,定义了一个可以执行 Java 字节码的环境。这个环境能够将 Java 字节码转换成特定平台上的机器码,从而实现了 “一次编写,到处运行” 的承诺。

Java 程序在运行时,需要将类文件(.class)加载到 JVM 的内存中。这个过程不仅涉及到类文件的读取,还包括对类的验证、准备、解析和初始化等一系列复杂的操作。这些步骤确保了类的正确性和安全性,并为类的执行做好准备。

类加载的步骤

类加载过程可以分为以下三个主要阶段:加载(Loading)、链接(Linking)和初始化(Initialization)。

每个阶段都有其独特的任务和目标。

(一)加载阶段

加载阶段是类加载过程的起始点。在这个阶段,JVM 需要将类的字节码从各种来源(如本地文件系统、网络等)读取进来,并将其转换为一个 Java 类的表示形式,存放在方法区(Method Area)中。

  1. 字节码来源:字节码可以来源于多个渠道,最常见的是由 Java 编译器生成的 class 文件。除此之外,字节码也可以在程序运行时动态生成,或者从网络中获取(例如在网页中运行的 Java Applet)。

  2. 类加载器:类加载器(ClassLoader)是加载阶段的核心组件。JVM 提供了多个内置的类加载器,包括启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用类加载器(Application ClassLoader)。每个类加载器都有其特定的职责和加载路径。

    • 启动类加载器:负责加载 Java 核心类库(如 java.lang.Object、java.lang.String 等),这些类位于 JRE 的 lib 目录下。

    • 扩展类加载器:负责加载 Java 扩展类库,这些类通常位于 JRE 的 lib/ext 目录下。

    • 应用类加载器:负责加载应用程序类路径(classpath)上的类文件。

  3. 双亲委派模型:类加载器采用了双亲委派模型(Parent Delegation Model)。当一个类加载器收到类加载请求时,它会先将请求委托给父类加载器。只有当父类加载器无法完成加载任务时,子类加载器才会尝试自己加载。这种模型保证了 Java 核心类库的类总是由启动类加载器加载,避免了类的多次加载和版本冲突问题。

(二)链接阶段

链接阶段的目标是将加载进来的类整合到 JVM 中,使其能够被虚拟机执行。

链接过程分为三个步骤:验证、准备和解析。

  1. 验证:验证阶段确保加载的类信息符合 JVM 的规范,并且不会危害虚拟机的安全。这一步骤对类的字节码进行严格检查,包括文件格式验证、元数据验证、字节码验证和符号引用验证。

    • 文件格式验证:检查类文件的格式是否正确,例如魔数是否正确、版本号是否受支持等。

    • 元数据验证:验证类的元数据(如字段、方法、访问修饰符等)是否符合语义规范。

    • 字节码验证:分析字节码指令,确保它们不会执行非法操作,如非法跳转、类型转换错误等。

    • 符号引用验证:确保解析动作能正确执行,即符号引用所指向的类、字段、方法确实存在。

  2. 准备:准备阶段为类的静态变量分配内存,并设置其初始值。这些初始值通常是该类型的默认值(如整数类型的默认值为 0,布尔类型的默认值为 false 等)。在这个阶段,JVM 还会为类的字段、方法等创建数据结构,以便后续的访问和操作。

  3. 解析:解析阶段将类、接口、字段和方法的符号引用转换为直接引用。符号引用是以符号形式表示的类、接口、字段或方法的名称和描述符等信息。直接引用则是指向内存中具体位置的指针或句柄,可以直接访问目标数据。解析过程包括对类或接口、字段、方法和接口方法的解析。

(三)初始化阶段

初始化阶段是类加载过程的最后一步。在这个阶段,JVM 执行类构造器(<clinit>() 方法),对类的静态变量进行初始化操作。类的初始化是按照 Java 代码的语义进行的,包括对静态变量的显式赋值和静态代码块的执行。

  1. 类构造器 <clinit>():类构造器是由编译器生成的特殊方法,它包含了类的静态变量初始化代码和静态代码块中的代码。JVM 会保证类构造器只被调用一次,并且在多线程环境下是线程安全的。

  2. 初始化触发条件:类的初始化并不是在类加载完成后立即执行的,而是需要满足一定的条件才会触发。以下是一些常见的触发类初始化的场景:

    • 遇到 new 指令,创建类的实例。

    • 调用类的静态方法。

    • 访问类的静态字段。

    • 子类的初始化会触发父类的初始化。

    • 使用反射 API 对类进行反射调用。

    • 初始化一个接口时,如果该接口含有 static-initializerdefault 方法,则会触发接口的初始化。

类加载的实践示例

接下来,我们通过一个简单的示例来展示类加载的过程。我们将使用以下代码片段来演示类的加载和初始化。

public class Singleton {private Singleton() {}
​private static class LazyHolder {static final Singleton INSTANCE = new Singleton();static {System.out.println("LazyHolder.<clinit>");}}
​public static Object getInstance(boolean flag) {if (flag) {return new LazyHolder[2];}return LazyHolder.INSTANCE;}
​public static void main(String[] args) {getInstance(true);System.out.println("----");getInstance(false);}
}

在上述代码中,我们定义了一个单例类 Singleton,并使用了懒汉式模式的 LazyHolder 内部类来实现延迟初始化。我们可以通过以下步骤来观察类加载和初始化的过程:

  1. 打印类加载日志:使用 JVM 参数 -verbose:class 来打印类加载的顺序。这个参数会告诉 JVM 在控制台输出每个类加载的信息。

    java -verbose:class Singleton
  2. 观察类初始化的触发时机:在 LazyHolder 内部类的静态代码块中打印特定字样,以便观察类初始化的时机。

  3. 修改字节码并重新加载:使用 jdisjasm 工具对类的字节码进行反汇编和重新汇编,观察修改后的类加载和初始化行为。

拓展

自定义类加载器

除了 JVM 提供的默认类加载器外,我们还可以创建自定义类加载器来实现特殊的类加载需求。自定义类加载器可以实现以下功能:

  • 对类文件进行加密和解密,以保护代码不被轻易篡改或窃取。

  • 动态生成类字节码,实现运行时类的动态加载。

  • 加载来自网络或其他非传统来源的类文件。

自定义类加载器通过继承 java.lang.ClassLoader 类并重写 findClass 方法来实现自定义的类加载逻辑。

public class CustomClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 自定义类加载逻辑byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();}return defineClass(name, classData, 0, classData.length);}
​private byte[] loadClassData(String name) {// 实现类数据的加载逻辑// 例如从文件系统、网络或加密存储中读取类文件return null;}
}

命名空间和类的唯一性

类加载器在 Java 中还提供了一个重要的功能:命名空间隔离。类的唯一性不仅由类的全名(包括包名和类名)决定,还与加载它的类加载器实例相关。这意味着,即使两个类具有相同的全名,如果它们是由不同的类加载器加载的,JVM 也会将它们视为不同的类。

这一特性在许多应用场景中非常有用,例如:

  • 在 Web 服务器中,不同的 Web 应用程序可以加载相同名称的类,而不会相互干扰。

  • 在 osgi 等模块化系统中,类加载器的命名空间隔离机制可以实现模块之间的版本隔离和依赖管理。

性能优化和工具支持

JVM 提供了丰富的工具来监控和优化类加载过程。

以下是一些常用的工具和参数:

  • -verbose:class:打印类加载的详细日志,帮助开发者了解类加载的顺序和时机。

  • -XX:+TraceClassLoading:输出类加载的追踪信息,提供更详细的类加载调试数据。

  • jstat:监控 JVM 的类加载和卸载统计信息,包括已加载的类数、卸载的类数、内存使用情况等。

  • jvisualvm:一个图形化工具,可以直观地显示 JVM 的运行状态,包括类加载信息、内存使用情况、线程状态等。

常见问题

新建数组是否会加载和初始化元素类?

在 Java 中,新建数组(如 new LazyHolder[2])会导致元素类的加载,但不会触发元素类的初始化。这是因为数组的创建只需要加载元素类的类信息,而不需要立即对数组元素进行初始化。只有当首次主动使用到数组元素类时(如访问数组元素或调用其静态方法),才会触发元素类的初始化。

类加载和链接的触发时机

类的加载和链接过程并不是在类被首次使用时才发生。实际上,类的加载可能在以下几种情况下被触发:

  • 当类作为 Java 应用程序的主类时,会在程序启动时被加载。

  • 当类被用作父类且子类被初始化时,父类会被加载和链接。

  • 当类被用作接口的实现类且接口被初始化时,类会被加载和链接。

链接过程中的验证、准备和解析步骤通常在类加载之后立即进行,但在某些情况下,解析步骤可能会延迟到首次使用相关符号引用时才执行。

如何避免类加载的性能瓶颈?

在大型 Java 应用中,类加载过程可能会成为性能瓶颈,尤其是在应用启动阶段。以下是一些优化类加载性能的建议:

  • 减少不必要的依赖:清理项目的类路径,移除未使用的库和类文件,可以减少类加载的数量和时间。

  • 优化类加载器的层次结构:合理设计类加载器的层次结构,避免过多的类加载器层级和复杂的委派链,可以提高类加载的效率。

  • 使用类预加载技术:对于一些关键类或频繁使用的类,可以在应用启动时提前加载,避免在运行时动态加载导致的延迟。

  • 监控和分析类加载过程:使用 JVM 提供的监控工具(如 jstatjvisualvm 等)来分析类加载的性能瓶颈,根据实际情况进行优化。

总结

Java 虚拟机的类加载机制是 Java 平台无关性和安全性的基石。通过加载、链接和初始化三个阶段,JVM 将类文件转换为内存中的类表示,并确保类的正确性和安全性。深入了解类加载的过程,不仅可以帮助我们更好地理解 Java 语言的底层运作机制,还能在实际开发中优化类加载性能,解决类加载相关的问题。

在实际应用中,掌握类加载机制的细节对于构建高效、可靠的 Java 应用至关重要。通过合理利用 JVM 提供的类加载器和工具,我们可以更好地管理类的加载过程,提升应用的性能和稳定性。希望本文能够为你深入探索 Java 虚拟机的类加载机制提供有价值的参考和指导。

如果你在类加载过程中遇到任何问题,或者对本文有任何疑问或建议,欢迎在评论区留言交流。让我们一起深入学习,共同进步!

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

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

相关文章

清华团队提出时序聚类数据库内高效方案,已被SIGMOD 2025接收

时间序列聚类是挖掘物联网等场景下频繁模式的关键技术&#xff0c;但现有SOTA方法&#xff08;如K-Shape&#xff09;面临两大瓶颈&#xff1a;1&#xff09;传统数据库因LSM-Tree存储导致时间戳无序&#xff0c;难以直接支持高效聚类&#xff1b;2&#xff09;跨时间范围查询需…

【阿里云大模型高级工程师ACP学习笔记】2.8 部署模型

一、学习目标 特别说明:这一章节是2025年3月官方重点更新的部分,几乎对内容重新翻新改造了一遍,重点突出了对于如何结合不同的阿里云产品来部署大模型进行了更加详细的介绍和对比,这里整理给大家,方便大家参考。 在备考阿里云大模型高级工程师ACP认证的过程中,学习《2.8 …

第T10周:数据增强

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 从 tensorflow.keras 中导入 layers 模块&#xff0c;包含了常用的神经网络层&#xff0c;用来搭建模型结构。 检查并列出系统中可用的物理 GPU 设备&#xff…

uniapp 支付宝小程序自定义 navbar 无效解决方案

如图&#xff1a; uniapp编译到支付宝小程序隐藏默认的导航栏失效了 解决方案&#xff1a; 在 pages.json 文件中找到 globalStyle 中加入以下代码&#xff1a; "mp-alipay": {"transparentTitle": "always","titlePenetrate":…

vue2 el-element中el-select选中值,数据已经改变但选择框中不显示值,需要其他输入框输入值才显示这个选择框才会显示刚才选中的值

项目场景&#xff1a; <el-table-column label"税率" prop"TaxRate" width"180" align"center" show-overflow-tooltip><template slot-scope"{row, $index}"><el-form-item :prop"InquiryItemList. …

centos7 离线安装python3 保留python2

一、事前准备&#xff1a; &#xff08;1&#xff09;查看centos具体版本 cat /etc/redhat-releaseCentOS Linux release 7.4.1708 (Core) &#xff08;2&#xff09;查看linux中当前python版本 centos7 默认安装python2.7.5 &#xff08;3&#xff09;查看python3的依赖&#…

十三种通信接口芯片——《器件手册--通信接口芯片》

目录 通信接口芯片 简述 基本功能 常见类型 应用场景 详尽阐述 1 RS485/RS422芯片 1. RS485和RS422标准 2. 芯片功能 3. 典型芯片及特点 4. 应用场景 5. 设计注意事项 6. 选型建议 2 RS232芯片 1. RS232标准 2. 芯片功能 3. 典型芯片及特点 4. 应用场景 5. 设计注意事项 6…

2025年RAG技术发展现状分析

2025年&#xff0c;大模型RAG&#xff08;检索增强生成&#xff09;技术经历了快速迭代与深度应用&#xff0c;逐渐从技术探索走向行业落地&#xff0c;同时也面临安全性和实用性的新挑战。以下是其发展现状的综合分析&#xff1a; 一、技术架构的持续演进 从单一到模块化架构 …

case和字符串操作

使用if选择结构 if [];then elif [];then #注意这个地方,java是else if else ; fi 使用for循环结构 使用for循环&#xff0c;语法结构如下所示&#xff1a; for 变量名 in 值1 值2 值3 #值的数量决定循环任务的次数 do命令序列 done#循环输出1到10 for i in {1..10} #注…

Stm32 烧录 Micropython

目录 前言 准备工作 开始操作 问题回顾 后记 前言 去年曾经尝试Pico制作openmv固件&#xff0c;由于知识储备不够最后失败了&#xff0c;留了一个大坑&#xff0c;有了前几天的基础&#xff0c;慢慢补齐知识&#xff0c;最近这一周一直在学习如何编译Stm固件并烧录到单片机…

盐化行业数字化转型规划详细方案(124页PPT)(文末有下载方式)

资料解读&#xff1a;《盐化行业数字化转型规划详细解决方案》 详细资料请看本解读文章的最后内容。 该文档聚焦盐化行业数字化转型&#xff0c;全面阐述了盐化企业信息化建设的规划方案&#xff0c;涵盖战略、架构、实施计划、风险及效益等多个方面&#xff0c;旨在通过数字化…

2025年人工智能火爆技术总结

2025年人工智能火爆技术总结&#xff1a; 生成式人工智能 生成式人工智能可生成高质量的图像、视频、音频和文本等多种内容。如昆仑万维的SkyReels-V2能生成无限时长电影&#xff0c;其基于扩散强迫框架&#xff0c;结合多模态大语言模型和强化学习等技术&#xff0c;在运动动…

边缘计算革命:大模型轻量化部署全栈实战指南

当ResNet-152模型能在树莓派4B上实现每秒27帧实时推理时&#xff0c;边缘智能时代真正到来。本文解析从模型压缩到硬件加速的完整技术栈&#xff0c;实测Transformer类模型在移动端的部署时延可压缩至16ms&#xff0c;揭示ARM芯片实现INT4量化的工程秘诀与十种典型场景优化方案…

边缘计算:数字世界的”末梢神经系统”解析-优雅草卓伊凡

边缘计算&#xff1a;数字世界的”末梢神经系统”解析-优雅草卓伊凡 一、边缘计算深度解析 1.1 边缘计算的定义与架构 边缘计算&#xff08;Edge Computing&#xff09;是一种分布式计算范式&#xff0c;它将数据处理能力从传统的集中式云数据中心推向网络边缘&#xff0c;更…

面试手撕——迭代法中序遍历二叉树

思路 访问顺序和处理顺序不一致导致迭代法难写&#xff0c;体现在总要先遍历根节点&#xff0c;才能访问左右孩子&#xff0c;用null标记&#xff0c;null标记的节点表示已经访问过了&#xff0c;下一次可以处理&#xff0c;所以在当前栈顶节点不是null的时候&#xff0c;都要…

AD系列:Windows Server 2025 安装AD CS角色和颁发证书

什么是 Active Directory 证书服务&#xff1f; Active Directory 证书服务 (AD CS) 是一个 Windows Server 角色&#xff0c;负责颁发和管理在安全通信和身份验证协议中使用的公钥基础结构 (PKI) 证书。 颁发和管理证书 数字证书可用于对电子文档和消息进行加密和数字签名&…

kubernetes》》k8s》》Service 、Ingress 区别

K8S>>Service 资料 K8S >>Ingress 资料 Ingress VS Service 物理层数据链路层网络层传输层会话层表示层应用层 Ingress是一种用于暴露HTTP和HTTPS路由的资源&#xff0c;它提供了七层&#xff08;应用层&#xff09;的负载均衡功能。Ingress可以根据主机名、…

【java WEB】恢复补充说明

Server 出现javax.servlet.http.HttpServlet", according to the project’s Dynamic Web Module facet version (3.0), was not found on the Java Build Path. 右键项目 > Properties > Project Facets。Dynamic Web Module facet version选4.0即可 还需要在serv…

VMware 创建虚拟机+简易安装Ubuntu的详细操作步骤

VMware 创建虚拟机安装Ubuntu的详细操作步骤 一、创建虚拟机1.1 点击创建新的虚拟机1.2 选择自定义创建虚拟机1.3 选择虚拟机的硬件兼容性1.4 安装客户机操作系统1.5 简易安装信息1.6 命名虚拟机名称1.7 处理器配置1.8 虚拟机内核选择1.9 网络类型1.9 选择I/O 控制器类型1.10 选…

GCC-C语言“自定义段”

一、起因 事情的起因是这样的,在看别人代码时,发现了一种很有意思的写法,因为本人主要是以应用层开发为主,所以对这种写法还是比较少见的,所以研究了一下,就牵扯出了一些知识点,这里先卖个关子,继续往下看。 二、经过 发现了一串这样的代码 static void do_mac(mcmd_…