当前位置: 首页 > article >正文

【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

类加载器详解(重点)

相关文章:

【JVM】Java类加载机制

【JVM】Java类加载机制 什么是类加载&#xff1f; 在 Java 的世界里&#xff0c;每一个类或接口在经过编译后&#xff0c;都会生成对应的 .class 字节码文件。 所谓类加载机制&#xff0c;就是 JVM 将这些 .class 文件中的二进制数据加载到内存中&#xff0c;并对其进行校验…...

Elasticsearch中的自定义分析器(Custom Analyzer)介绍

在 Elasticsearch 中,自定义分析器(Custom Analyzer) 是一种可配置的文本处理组件,允许用户通过组合分词器(Tokenizer)、过滤器(Token Filter)和字符过滤器(Character Filter)来定义特定的文本分析逻辑。这使得 Elasticsearch 能够针对不同语言、业务场景或特殊需求,…...

《C++初阶之入门基础》【C++的前世今生】

【C的前世今生】目录 前言&#xff1a;---------------起源---------------一、历史背景二、横空出世---------------发展---------------三、标准立世C98&#xff1a;首个国际标准版本C03&#xff1a;小修订版本 四、现代进化C11&#xff1a;现代C的开端C14&#xff1a;对C11的…...

Apache APISIX

目录 Apache APISIX是什么&#xff1f; Lua Lua 的主要特点&#xff1a; Lua 的常见应用&#xff1a; CVE-2020-13945(Apache APISIX默认API Token导致远程Lua代码执行) ​编辑Lua脚本解析 CVE-2021-45232(Apache APISIX Dashboard API权限绕过导致RCE) Apache …...

如何在 git dev 中创建合并请求

先将 自己的代码 推到 自己的远程的 分支上 在 创建 合并请求 根据提示 将 自己的远程的 源码 合并到 对应的分支上 然后 创建 合并请求 等待 对应的 人 来 进行合并就行...

基于nlohmann/json 实现 从C++对象转换成JSON数据格式

C对象的JSON序列化与反序列化 基于JsonCpp库实现C对象序列化与反序列化 JSON 介绍 JSON作为一种轻量级的数据交换格式&#xff0c;在Web服务和应用程序中广泛使用。 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读…...

Java枚举类映射MySQL的深度解析与实践指南

Java枚举类映射MySQL的深度解析与实践指南 一、枚举类型映射的四大核心策略 1. 序数映射法&#xff08;ordinal映射&#xff09; ​​实现原理​​&#xff1a;存储枚举值的下标顺序 public enum OrderStatus {PENDING, // 存储为0PROCESSING, // 存储为1SHIPPED, //…...

代码训练LeetCode(21)跳跃游戏2

代码训练(21)LeetCode之跳跃游戏2 Author: Once Day Date: 2025年6月4日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 45. 跳跃游戏 II - 力扣&#xff08;LeetCode&#xff09;力扣 (LeetCode) 全球极客挚爱…...

【HarmonyOS 5】鸿蒙APP使用【团结引擎Unity】开发的案例教程

以下是基于团结引擎开发鸿蒙Unity应用的详细案例教程&#xff0c;整合环境配置、工程适配、跨语言通信等核心环节 一、环境配置&#xff08;关键前置步骤&#xff09; 1. ‌工具安装‌ ‌工具‌‌版本要求‌‌作用‌团结引擎Hub≥1.2.3Unity鸿蒙项目构建管理DevEco Studio≥…...

《T/CI 404-2024 医疗大数据智能采集及管理技术规范》全面解读与实施分析

规范背景与详细信息 《T/CI 404-2024 医疗大数据智能采集及管理技术规范》是由中国国际科技促进会联合河南科技大学、河南科技大学第一附属医院、深圳市人民医院等十余家医疗机构与企业共同制定的团体标准,于2024年5月正式发布实施。该规范是我国医疗大数据领域的重要技术标准…...

国产三维CAD皇冠CAD在「金属压力容器制造」建模教程:蒸汽锅炉

面对蒸汽锅炉设计中复杂的曲面封头、密集的管板开孔、多变的支撑结构以及严格的强度与安全规范&#xff08;如GB150、ASME等&#xff09;&#xff0c;传统二维设计手段往往捉襟见肘&#xff0c;易出错、效率低、协同难。国产三维CAD皇冠CAD&#xff08;CrownCAD&#xff09;凭借…...

Mysql避免索引失效

1. 在索引列上使用函数或表达式 问题描述 SELECT * FROM users WHERE YEAR(create_time) 2023; 如果create_time列上有索引&#xff0c;上述查询会导致索引失效&#xff0c;因为MySQL无法直接利用索引的B树结构。 解决方法 将函数应用于条件值&#xff0c;而不是列&#…...

python爬虫:Ruia的详细使用(一个基于asyncio和aiohttp的异步爬虫框架)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Ruia概述1.1 Ruia介绍1.2 Ruia特点1.3 安装Ruia1.4 使用案例二、基本使用2.1 Request 请求2.2 Response - 响应2.3 Item - 数据提取2.4 Field 提取数据2.5 Spider - 爬虫类2.6 Middleware - 中间件三、高级功能3.1 …...

C++中单例模式详解

在C中&#xff0c;单例模式 (Singleton Pattern) 确保一个类只有一个实例&#xff0c;并提供一个全局访问点来获取这个实例。这在需要一个全局对象来协调整个系统行为的场景中非常有用。 为什么要有单例模式&#xff1f; 在许多项目中&#xff0c;某些类从逻辑上讲只需要一个实…...

舆情监控系统爬虫技术解析

之前我已经详细解释过爬虫在系统中的角色和技术要点&#xff0c;这次需要更聚焦“如何实现”这个动作。 我注意到上次回复偏重架构设计&#xff0c;这次应该拆解为更具体的操作步骤&#xff1a;从目标定义到数据落地的完整流水线。尤其要强调动态调度这个容易被忽视的环节——…...

Windows上用FFmpeg采集摄像头推流 → MediaMTX服务器转发流 → WSL2上拉流播放

1. Windows上 FFmpeg 推流&#xff08;摄像头采集&#xff09; 设备名称可用 ffmpeg -list_devices true -f dshow -i dummy 查询&#xff0c;假设为Integrated Camera 采集推流示例&#xff08;推RTMP到MediaMTX&#xff09;&#xff1a; ffmpeg -rtbufsize 100M -f dshow …...

cpp多线程学习

1.thread std::thread是 C11 引入的跨平台线程管理类&#xff0c;封装了操作系统的线程 API&#xff08;如 pthread、Windows 线程&#xff09;&#xff0c;提供统一的线程操作接口。线程的生命周期由join()和detach()控制。 thread在创建时就开始执行 join()&#xff1a;阻…...

Vue3中Ant-design-vue的使用-附完整代码

前言 首先介绍一下什么是Ant-design-vue Ant Design Vue 是基于 Vue 3 的企业级 UI 组件库&#xff08;同时兼容 Vue 2&#xff09;&#xff0c;是蚂蚁金服开源项目 Ant Design 的 Vue 实现版本。它遵循 Ant Design 的设计规范&#xff0c;提供丰富的组件和高质量的设计体系&…...

k8s热更新-subPath 不支持热更新

文章目录 k8s热更新-subPath 不支持热更新背景subPath 不支持热更新1. 为什么 subPath 不支持热更新&#xff1f;2. 挂载整个目录为何支持热更新&#xff1f;使用demo举例&#xff1a;挂载整个目录&#xff08;不使用 subPath&#xff09; k8s热更新-subPath 不支持热更新 背景…...

Redis Sorted Set 深度解析:从原理到实战应用

Redis Sorted Set 深度解析&#xff1a;从原理到实战应用 在 Redis 丰富的数据结构家族中&#xff0c;Sorted Set&#xff08;有序集合&#xff09;凭借独特的设计和强大的功能&#xff0c;成为处理有序数据场景的得力工具。无论是构建实时排行榜&#xff0c;还是实现基于时间的…...

docker中组合这几个命令来排查 import 模块失败 的问题

pwd ls echo $PYTHONPATH这三个命令是你在 Linux 或 Docker 容器中常用来「查看环境状态」的基础命令。 ✅ 1. echo $PYTHONPATH &#x1f50d; 含义 这是在查看当前的 Python 模块搜索路径。 &#x1f9e0; 分解解释&#xff1a; echo&#xff1a;打印某个变量的值&#x…...

若依框架修改模板,添加通过excel导入数据功能

版本&#xff1a;我后端使用的是RuoYi-Vue-fast版本&#xff0c;前端是RuoYi-Vue3 需求: 我需要每个侧边栏功能都需要具有导入excel功能&#xff0c;但是若依只有用户才具备&#xff0c;我需要代码生成的每个功能都拥有导入功能。​ 每次生成一个一个改实在是太麻烦了。索性…...

web全栈开发学习-01html基础

背景 最近在付费网站学习web全栈开发&#xff0c;记录一下阶段性学习。今天刚好学完html基础&#xff0c;跟着教程画了个基础的网站。 样品展示: 开发工具 vscode Visual Studio Code - Code Editing. Redefined 常用插件 Prettier&#xff1a;格式优化 Live Sever:实时调…...

基于Socketserver+ThreadPoolExecutor+Thread构造的TCP网络实时通信程序

目录 介绍&#xff1a; 源代码&#xff1a; Socketserver-服务端代码 Socketserver客户端代码&#xff1a; 介绍&#xff1a; socketserver是一种传统的传输层网络编程接口&#xff0c;相比WebSocket这种应用层的协议来说&#xff0c;socketserver比较底层&#xff0c;soc…...

[Java 基础]枚举

枚举是一种特殊的类&#xff0c;表示一组固定的常量。枚举跟普通类一样可以用自己的变量、方法和构造函数&#xff0c;构造函数只能使用 private 访问修饰符&#xff0c;所以外部无法调用。 现实生活中的例子&#xff1a; 一周七天&#xff08;MONDAY ~ SUNDAY&#xff09; …...

多线程环境中,如果多个线程同时尝试向同一个TCP客户端发送数据,添加同步机制

原代码 public async Task SendToClientAsync(TcpClient targetClient, byte[] data, int offset, int length) {try{// 1. 检查客户端是否有效if (targetClient null || !targetClient.Connected){Console.WriteLine("Cannot send: client is not connected");ret…...

【含文档+PPT+源码】基于微信小程序的旅游论坛系统的设计与实现

项目介绍 本课程演示的是一款基于微信小程序的旅游论坛系统的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 …...

贝叶斯优化+LSTM+时序预测=Nature子刊!

贝叶斯优化与LSTM的融合在时间序列预测领域取得了显著成效&#xff0c;特别是在处理那些涉及众多超参数调整的复杂问题时。 1.这种结合不仅极大提高了预测的精确度&#xff0c;还优化了模型训练流程&#xff0c;提升了效率和成本效益。超参数优化的新篇章&#xff1a;LSTM因其…...

NodeJS全栈WEB3面试题——P3Web3.js / Ethers.js 使用

3.1 Ethers.js 和 Web3.js 的主要区别是什么&#xff1f; 比较点Ethers.jsWeb3.js体积更轻量&#xff0c;适合前端较大&#xff0c;加载慢&#xff0c;适合 Node文档文档简洁、现代化&#xff0c;支持 TypeScript文档丰富&#xff0c;但不够现代化模块化设计高度模块化&#x…...

Quick UI 组件加载到 Axure

将 Quick UI 组件加载到 Axure 的完整指南 Axure 支持通过自定义元件库加载外部 UI 组件库&#xff08;如 Quick UI&#xff09;&#xff0c;以下是详细的操作流程&#xff1a; 一、准备工作 获取 Quick UI 组件库文件&#xff1a; 下载 .rplib 格式的 Quick UI 元件库文件&a…...