【JVM】Java类加载机制

【JVM】Java类加载机制

什么是类加载?

在 Java 的世界里,每一个类或接口在经过编译后,都会生成对应的 .class 字节码文件。

所谓类加载机制,就是 JVM 将这些 .class 文件中的二进制数据加载到内存中,并对其进行校验、解析、初始化等一系列操作。最终,每个类都会在方法区(或元空间)中保留一份结构化的类信息(元数据),并在Java 堆中创建一个 java.lang.Class 类型的对象,供程序运行时使用。

从 JVM 的角度看,一个类的生命周期包括以下 7 个阶段:

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载

其中,前五个阶段(加载、验证、准备、解析、初始化)统称为类的加载过程。其中,验证、准备和解析这三个阶段可以统称为连接
请添加图片描述

需要注意的是,类加载的五个阶段并不是严格按顺序线性执行的,而是相互交叉、动态混合的过程

例如:

  • 部分验证操作(如文件格式验证)可能在加载 .class 文件的过程中就被触发。
  • 符号引用的解析可能被延迟到真正使用时才发生。

类加载的详细过程

1.加载

  • 任务: 查找并加载类的二进制数据(通常是 .class 文件,但来源可以多样)。

  • 过程:

    • 通过类的全限定名(如 java.lang.Stringcom.example.MyClass)获取定义此类的二进制字节流。这个字节流来源可以是文件系统(最常见的)、网络、ZIP/JAR包、运行时计算生成(动态代理)、数据库等等。

    类的全限定名就是类的完整名称,即:包名 + 类名(如 java.lang.Stringcom.example.MyClass

    • 将这个字节流所代表的静态存储结构转换为方法区 的运行时数据结构。
    • 堆(Heap) 内存中创建一个代表这个类的 java.lang.Class 对象,作为方法区中这个类的各种数据的访问入口。常用的 .classgetClass() 返回的就是这个对象。
    public class CustomLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) {// 1. 获取字节流(可来自文件/网络/数据库等)byte[] bytes = loadClassBytes(name); // 2. 转换为方法区数据结构// 3. 创建Class对象return defineClass(name, bytes, 0, bytes.length);}
    }
    
  • 关键点:

    • 类加载器 完成。

    • 加载的最终产物是堆中的 Class 对象。

    • 类的加载是懒惰的,首次用到时才会加载:

      1. 使用了类.class
      2. 用类加载器的 loadClass 方法加载类
      3. 满足类的初始化条件(后文有详细介绍)

      参考:2-类加载_验证类加载是懒惰的_哔哩哔哩_bilibili

2.连接

2.1 验证
  • 任务: 确保加载的字节码信息符合 JVM 规范,是安全、无害的,不会危害虚拟机自身安全。
  • 内容:
    • 文件格式验证(魔数、版本号等)
    • 元数据验证(语义分析,如是否有父类、是否继承了final类、是否实现接口所有方法等)
    • 字节码验证(数据流和控制流分析,确保逻辑正确,如操作数栈类型匹配、跳转指令目标合理等)
    • 符号引用验证(检查常量池中的符号引用能否找到对应的类、字段、方法等)。
  • 重要性: 保护JVM安全,防止恶意代码或损坏的字节码文件导致JVM崩溃或执行危险操作。虽然耗点时间,但对系统稳定性至关重要。
2.2 准备
  • 任务: 为类的静态变量分配内存,并设置默认初始值
  • 过程:
    • 在方法区中为这些静态变量分配内存空间。
    • 设置默认初始值:
      • 基本类型:int -> 0, long -> 0L, float -> 0.0f, double -> 0.0d, char -> '\u0000', boolean -> false
      • 引用类型:null
  • 关键点:
    • 这里分配内存并初始化的是类变量(static变量),不是实例变量
    • 初始化的值是默认零值,不是代码中显式赋的值(如 public static int value = 123;,在准备阶段后 value0,赋值 123 的操作发生在后面的初始化阶段)。
    • 如果静态变量是 final 修饰的基本类型或 String 常量,并且在编译时就能确定值(如 public static final int CONSTANT = 100;),那么这个值会直接在准备阶段被赋予(此时 CONSTANT 就是 100)。
2.3 解析
  • 任务: 将常量池内的符号引用 替换为直接引用
  • 符号引用与直接引用:
    • 符号引用: 一组描述被引用目标(类、字段、方法)的符号。例如,java/lang/Object(类名)、toString:()Ljava/lang/String;(方法名和描述符)。它只是一个字面量引用,与内存布局无关。
    • 直接引用: 一个能直接定位到目标(类在方法区的地址、字段或方法在内存中的偏移量或句柄)的指针、偏移量或句柄。它是与JVM运行时内存布局相关的。
  • 过程: JVM 查找符号引用所指向的类、字段或方法的实际位置,并将常量池中的符号引用替换为指向该位置的直接引用。
  • 时机: 解析阶段可以在初始化之前完成,也可以在初始化之后完成(甚至延迟到第一次实际使用该符号引用时),这取决于 JVM 的实现策略(“及早解析”或“惰性解析”)。

3.初始化

  • 任务: 执行类的初始化代码,主要是执行类构造器 <clinit>() 方法。
  • <clinit>() 方法:
    • 由编译器自动收集类中所有类变量(static变量)的显式赋值动作静态代码块(static {} 块) 中的语句合并生成。
    • 顺序:按源代码中出现的顺序执行。
    • 父类的 <clinit>() 优先于子类的执行。
    • 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确地加锁、同步(即线程安全)。如果一个线程正在执行它,其他线程会阻塞等待。
  • 触发时机(严格规定): 类只有在以下 6 种情况之一发生时,才会立即进行初始化(加载和连接可能更早发生):
    1. 创建类的实例 (new)。
    2. 访问类的静态变量(读取或赋值),除非该静态变量是 final 常量并且在编译期就能确定值(常量传播优化)。
    3. 调用类的静态方法 (static 方法)。
    4. 使用反射 (Class.forName("..."), getMethod 等) 对类进行反射调用。
    5. 初始化一个类的子类时,会触发其父类的初始化。
    6. JVM 启动时被标明为启动类(包含 main() 方法的那个类)。
  • 关键点:
    • 这是类加载过程的最后一步
    • 此时才真正执行程序员的代码逻辑(静态赋值、静态块)。
    • 之前的“准备”阶段只是分配内存并赋零值,这里是赋程序员定义的值。

类加载器

在类加载的第一个阶段——加载中,JVM 需要根据类的全限定名,找到并读取其对应的字节码文件(.class 文件)。

这个查找和读取 .class 字节流的工作,正是由类加载器来完成的。

请添加图片描述

JVM 中内置了三个重要的 ClassLoader

  1. Bootstrap ClassLoader (启动类/引导类加载器):
    • 用原生代码(C/C++)实现,是 JVM 自身的一部分。
    • 负责加载 JAVA_HOME/lib 目录下的核心 Java 库(如 rt.jar, charsets.jar)或 -Xbootclasspath 参数指定的路径中的类。
    • 是最高级别的加载器,没有父加载器
    • null 表示: 在 Java 代码中试图获取它的引用时,返回 null
  2. Extension ClassLoader (扩展类加载器):
    • sun.misc.Launcher$ExtClassLoader 实现(Java)。
    • 负责加载 JAVA_HOME/lib/ext 目录下的扩展库,或 java.ext.dirs 系统变量指定的路径中的所有类库。
    • 父加载器是 Bootstrap ClassLoader
  3. Application ClassLoader (应用程序类加载器 / 系统类加载器):
    • sun.misc.Launcher$AppClassLoader 实现(Java)。
    • 负责加载用户类路径(ClassPath) 上所指定的类库。这是我们程序中默认的类加载器。
    • 父加载器是 Extension ClassLoader
    • 通过 ClassLoader.getSystemClassLoader() 可以获取到它。

除了这三种类加载器之外,用户还可以加入自定义的类加载器来进行拓展,以满足自己的特殊需求:

除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自 ClassLoader抽象类。如果我们要自定义自己的类加载器,需要继承 ClassLoader抽象类。

ClassLoader 类中有两个核心方法:

  • protected Class<?> loadClass(String name, boolean resolve)

    加载指定名称的类。该方法实现了双亲委派模型:会先委托给父加载器尝试加载,如果父加载器无法完成,才会调用自身的 findClass() 方法进行加载。

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 先检查当前类是否已经加载Class<?> c = findLoadedClass(name);if (c == null) {try {if (parent != null) {// 先让父类加载器尝试加载c = parent.loadClass(name, false);} else {// 如果没有父加载器(即引导类加载器),使用 bootstrap 加载c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 忽略异常,进入下一步由自己加载}if (c == null) {// 如果父加载器无法加载,再尝试使用当前类加载器加载c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}
    }
    
  • protected Class<?> findClass(String name)
    根据类名查找类的定义并返回对应的 Class 对象。默认实现是抛出 ClassNotFoundException,需要我们在子类中重写。

    protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);
    }
    

如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

双亲委派模型简介

双亲委派模型是 Java 类加载机制的重要组成部分,它通过委派父加载器优先加载类的方式,实现了两个关键的安全目标:避免类的重复加载和防止核心 API 被篡改。

  • 工作流程: 当一个类加载器收到加载类的请求时:

    1. 它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
    2. 每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器
    3. 只有当父加载器反馈自己无法完成这个加载请求(在它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载
  • 核心思想:向上委派,向下加载”。

    请添加图片描述

  • 核心目的:

    • 保证基础类的唯一性和安全性: 防止用户自定义一个与核心库(如 java.lang.Object)同名的类被加载,从而覆盖核心库的行为(沙箱安全机制)。
    • 避免重复加载: 父加载器已经加载过的类,子加载器就不会再加载(在同一个命名空间内)。

内容参考

【JVM】Java类加载机制这块算是玩明白了_哔哩哔哩_bilibili

类加载器详解(重点)

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

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

相关文章

vue的监听属性watch的详解

文章目录 1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景 1. 监听属性 watch watch 是一个对象&#xff0c;键是需要观察的表达式&#xff0c;用于观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。回调函数的参数是新值和旧值。值也可以是方法名&am…

如何在 Ubuntu 24.04 服务器上安装 Apache Solr

Apache Solr 是一个免费、开源的搜索平台&#xff0c;广泛应用于实时索引。其强大的可扩展性和容错能力使其在高流量互联网场景下表现优异。 Solr 基于 Java 开发&#xff0c;提供了分布式索引、复制、负载均衡及自动故障转移和恢复等功能。 本教程将指导您如何在 Ubuntu 24.…

Linux内核中TCP三次握手的实现机制详解

TCP三次握手是建立可靠网络连接的核心过程,其在内核中的实现涉及复杂的协议栈协作。本文将深入分析Linux内核中三次握手的实现机制,涵盖客户端与服务端的分工、关键函数调用、协议号验证及数据包处理流程。 一、三次握手的整体流程 三次握手分为三个阶段,客户端与服务端通过…

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…

优化 Spring Boot API 性能:利用 GZIP 压缩处理大型有效载荷

引言 在构建需要处理和传输大量数据的API服务时&#xff0c;响应时间是一个关键的性能指标。一个常见的场景是&#xff0c;即使后端逻辑和数据库查询已得到充分优化&#xff0c;当API端点返回大型数据集&#xff08;例如&#xff0c;数千条记录的列表&#xff09;时&#xff0…

【WPF】WPF 项目实战:构建一个可增删、排序的光源类型管理界面(含源码)

&#x1f4a1;WPF 项目实战&#xff1a;构建一个可增删、排序的光源类型管理界面&#xff08;含源码&#xff09; 在实际的图像处理项目中&#xff0c;我们经常需要对“光源类型”进行筛选或管理。今天我们来一步步构建一个实用的 WPF 界面&#xff0c;实现以下功能&#xff1…

C++23 已弃用特性

文章目录 1. std::aligned_storage 与 std::aligned_union1.1 特性介绍1.2 被弃用的原因1.3 替代方案 2. std::numeric_limits::has_denorm2.1 特性介绍2.2 被弃用的原因 3. 总结 C23 已弃用特性包括&#xff1a;std::aligned_storage、std::aligned_union 与 std::numeric_lim…

十三、【核心功能篇】测试计划管理:组织和编排测试用例

【核心功能篇】测试计划管理:组织和编排测试用例 前言准备工作第一部分:后端实现 (Django)1. 定义 `TestPlan` 模型2. 生成并应用数据库迁移3. 创建 `TestPlanSerializer`4. 创建 `TestPlanViewSet`5. 注册路由6. 注册到 Django Admin第二部分:前端实现 (Vue3)1. 创建 `Test…

STM32最小CLion开发环境

文章目录 1 必须文件2 工具链3 CLion 全局配置4 CLion 新项目配置ST-Link 调试 5 点亮 LED6 分析 elf 文件7 项目模板 1 必须文件 ST 提供的头文件支持 MDK-ARM, GCC, IAR 3种编译器, 下面采用 GCC 编译器 Arm GNU Toolchain Downloads – Arm Developer 或 安装包版 调试器服…

核函数:解锁支持向量机的强大能力

在机器学习的世界中&#xff0c;支持向量机&#xff08;SVM&#xff09;是一种强大的分类算法&#xff0c;而核函数则是其背后的“魔法”&#xff0c;让 SVM 能够处理复杂的非线性问题。今天&#xff0c;我们就来深入探讨核函数的奥秘&#xff0c;看看它们是如何帮助 SVM 在高维…

【Go-6】数据结构与集合

6. 数据结构与集合 数据结构是编程中用于组织和存储数据的方式&#xff0c;直接影响程序的效率和性能。Go语言提供了多种内置的数据结构&#xff0c;如数组、切片、Map和结构体&#xff0c;支持不同类型的数据管理和操作。本章将详细介绍Go语言中的主要数据结构与集合&#xf…

3. 简述node.js特性与底层原理

&#x1f63a;&#x1f63a;&#x1f63a; 一、Node.js 底层原理&#xff08;简化版&#xff09; Node.js 是一个 基于 Chrome V8 引擎构建的 JavaScript 运行时&#xff0c;底层核心由几部分组成&#xff1a; 组成部分简要说明 1.V8 引擎 将 JS 编译成机器码执行&#xff0…

Web开发主流前后端框架总结

&#x1f5a5; 一、前端主流框架 前端框架的核心是提升用户界面开发效率&#xff0c;实现高交互性应用。当前三大主流框架各有侧重&#xff1a; React (Meta/Facebook) 核心特点&#xff1a;采用组件化架构与虚拟DOM技术&#xff08;减少真实DOM操作&#xff0c;优化渲染性能&…

大语言模型备案与深度合成算法备案的区别与联系

“什么情况下做算法备案&#xff1f;” “什么情况下做大模型备案呢&#xff1f;” 进行大模型备案的企业必然要进行算法备案&#xff0c;而进行算法备案的企业则需根据其提供的服务性质判断是否需要进行大模型备案。 算法备案与大模型备案已经是个老生常谈的话题了&#xf…

微软PowerBI考试 PL300-Power BI 入门

Power BI 入门 上篇更新了微软PowerBI考试 PL-300学习指南&#xff0c;今天分享PowerBI入门学习内容。 简介 Microsoft Power BI 是一个完整的报表解决方案&#xff0c;通过开发工具和联机平台提供数据准备、数据可视化、分发和管理。 Power BI 可以从使用单个数据源的简单…

【Hive入门】

之前实习写的笔记&#xff0c;上传留个备份。 1. 使用docker-compose快速搭建Hive集群 使用docker快速配置Hive环境 拉取镜像 2. Hive数据类型 隐式转换&#xff1a;窄的可以向宽的转换显式转换&#xff1a;cast 3. Hive读写文件 SerDe:序列化&#xff08;对象转为字节码…

设计模式——简单工厂模式(创建型)

摘要 本文主要介绍了简单工厂模式&#xff0c;包括其定义、结构、实现方式、适用场景、实战示例以及思考。简单工厂模式是一种创建型设计模式&#xff0c;通过工厂类根据参数决定创建哪一种产品类的实例&#xff0c;封装了对象创建的细节&#xff0c;使客户端无需关心具体类的…

抽象工厂模式与策略模式结合使用小案例

目录 1.前言1.示例说明1.1定义通用接口1.2 定义抽象工厂1.3 支付宝实现1.4 微信实现1.5 客户端使用代码&#xff08;组合使用&#xff09;1.6 示例结果输出1.7 总结 1.前言 上一篇章就通过简单的案例来了解抽象工厂模式和策略模式的使用&#xff0c;现在就用个支付场景的小案例…

通过WiFi无线连接小米手机摄像头到电脑的方法

通过WiFi无线连接小米手机摄像头到电脑的方法 以下是基于Scrcpy和DroidCam两种工具的无线连接方案&#xff0c;需提前完成开发者模式与USB调试的开启&#xff08;参考原教程步骤&#xff09;&#xff1a; 方法一&#xff1a;Scrcpy无线投屏&#xff08;无需手机端安装&#xf…

2025软件供应链安全最佳实践︱证券DevSecOps下供应链与开源治理实践

项目背景&#xff1a;近年来&#xff0c;云计算、AI人工智能、大数据等信息技术的不断发展、各行各业的信息电子化的步伐不断加快、信息化的水平不断提高&#xff0c;网络安全的风险不断累积&#xff0c;金融证券行业面临着越来越多的威胁挑战。特别是近年以来&#xff0c;开源…