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

《深入理解JVM》实战笔记(二): 类加载机制与类加载器

序言

Java 语言的强大之处之一在于其动态加载的能力,使得 Java 程序可以在运行时加载新的类,而不需要在编译时确定所有的类信息。这一切都离不开 JVM 的类加载机制。本篇博客将详细探讨 JVM 的类加载过程以及类加载器的工作原理,帮助你更深入地理解 Java 的动态性和可扩展性。


1. 类加载机制概述

在 Java 语言中,类的生命周期主要包括加载(Loading)、链接(Linking)和初始化(Initialization)三个阶段。JVM 通过类加载器(ClassLoader)来完成这个过程,使 Java 具有动态加载和模块化的特性。

1.1 类的生命周期

一个类从被加载到 JVM,到最终被卸载,经历了如下几个阶段:

  1. 加载(Loading):JVM 通过类加载器读取 class 文件,并生成 java.lang.Class 对象。

  2. 链接(Linking)

    • 验证(Verification):确保 class 文件的字节码符合 JVM 规范,保证安全性。

    • 准备(Preparation):为类的静态变量分配内存,并初始化默认值(不包括赋值语句)。

    • 解析(Resolution):将常量池中的符号引用解析为直接引用。

  3. 初始化(Initialization):执行类的静态初始化代码,即 static 变量赋值和 static 代码块。

  4. 使用(Using):类被实例化、调用方法等。

  5. 卸载(Unloading):当类不再被引用时,GC 可能会回收它(通常仅适用于自定义类加载器加载的类)。


2. 类加载过程详解

类加载是 JVM 将字节码数据从静态存储结构(如 class 文件)转换为运行时数据结构的过程。这个过程不仅包含简单的二进制读取,还需要完成复杂的校验、内存分配和符号解析等操作。下面将详细解析每个阶段的实现细节。

2.1 加载(Loading)

加载阶段是类加载的第一个环节,核心任务是通过全限定名(如 java.lang.String)获取类的二进制字节流,并将其转换为方法区的运行时数据结构,最终在堆中生成一个 java.lang.Class 对象作为访问入口。

关键步骤
  1. 获取二进制字节流:类加载器通过全限定名定位资源,可能来自文件系统、JAR 包、网络或动态生成(如动态代理)。

  2. 解析为方法区结构:将字节流代表的静态存储结构转换为方法区的运行时数据结构。

  3. 创建 Class 对象:在堆中创建该类的 Class 对象,作为程序访问方法区数据的接口。

触发条件
  • 首次创建类的实例(new 关键字)。

  • 访问类的静态变量或静态方法。

  • 通过反射(如 Class.forName())显式加载类。

  • 子类初始化时触发父类的加载。


2.2 链接(Linking)

链接阶段分为三个子阶段:验证(Verification)、准备(Preparation)、解析(Resolution)。

2.2.1 验证(Verification)

验证确保字节码符合 JVM 规范且不会危害虚拟机安全,包含以下四个子阶段:

  1. 文件格式验证

    • 检查魔数(0xCAFEBABE)是否合法,确认是否为有效的 class 文件。

    • 检查主次版本号是否在当前 JVM 支持范围内。

    • 检查常量池中的常量是否有不被支持的类型。

  2. 元数据验证

    • 检查类是否有父类(除 java.lang.Object 外所有类必须有父类)。

    • 检查父类是否被 final 修饰(若被 final 修饰则不能继承)。

    • 确保字段、方法是否与父类冲突(如覆盖 final 方法)。

  3. 字节码验证

    • 确保操作数栈的数据类型与指令兼容(如不会出现 int 入栈却按 long 处理)。

    • 检查跳转指令是否指向合法位置(如不会跳转到方法体外的字节码)。

  4. 符号引用验证

    • 检查符号引用能否找到对应的类、方法或字段。

    • 确保访问权限合法(如 private 方法是否被外部类访问)。

2.2.2 准备(Preparation)

准备阶段为类的静态变量分配内存并设置初始值(零值):

  • 基本类型:int 初始化为 0boolean 初始化为 false

  • 引用类型:初始化为 null

  • 例外:若静态变量被 final 修饰且在编译期已知(如 static final int VALUE = 123),则直接赋值为指定值。

2.2.3 解析(Resolution)

解析阶段将常量池中的符号引用替换为直接引用(内存地址偏移量或句柄):

  • 类/接口解析:若符号引用指向类,需先完成该类的加载。

  • 字段解析:解析字段所属的类,并验证是否存在及权限。

  • 方法解析:与方法表关联,检查方法是否存在及权限。

  • 接口方法解析:类似方法解析,但需考虑接口的多继承特性。

注意:解析阶段可能在初始化之后触发(如动态绑定或 invokedynamic 指令)。


2.3 初始化(Initialization)

初始化阶段执行类的构造器 <clinit>() 方法,该方法由编译器自动生成,合并所有静态变量的赋值语句和静态代码块。

关键特性
  • 线程安全:JVM 通过加锁确保 <clinit>() 只执行一次。

  • 顺序性:父类的 <clinit>() 优先于子类执行。

  • 主动触发:只有以下情况会触发初始化(加载和链接可能提前完成):

    • newgetstaticputstaticinvokestatic 指令。

    • 反射调用类时(如 Class.forName("MyClass"))。

    • 主类(包含 main() 方法的类)在启动时立即初始化。

示例
public class Example {static int a = 1;         // 准备阶段 a=0 → 初始化阶段 a=1  static final int b = 2;   // 准备阶段直接赋值 b=2  static {System.out.println("Static block executed.");}
}

2.4 使用(Using)与卸载(Unloading)

  • 使用阶段:类完成初始化后,可被用于创建对象、调用方法等。

  • 卸载条件

    • 类的所有实例已被回收。

    • 类的 Class 对象未被引用(如无反射持有)。

    • 加载该类的 ClassLoader 实例已被回收。

    注意:由启动类加载器(Bootstrap)加载的类通常不会被卸载。


3. JVM 类加载器(ClassLoader)

类加载器是 Java 实现动态加载的关键组件。它的主要作用是负责查找和加载类的字节码,并定义类对象

3.1 类加载器的层级结构

JVM 的类加载器是分层委托模型(双亲委派机制),主要包括以下几种类加载器:

  1. Bootstrap ClassLoader(启动类加载器)

    • 负责加载 JAVA_HOME/lib 目录下的 rt.jar(Java 核心类库,如 java.lang.*)。

    • 由 C/C++ 语言实现,无法在 Java 代码中获取它的实例。

  2. ExtClassLoader(扩展类加载器)

    • 负责加载 JAVA_HOME/lib/ext/ 目录中的扩展类。

    • 可通过 ClassLoader.getSystemClassLoader().getParent() 获取。

  3. AppClassLoader(应用类加载器)

    • 负责加载应用程序的 classpath 目录下的类。

    • ClassLoader.getSystemClassLoader() 返回的就是它。

  4. 自定义类加载器(用户自定义 ClassLoader)

    • 通过继承 ClassLoader,可实现自定义的类加载逻辑。

    • 适用于类热替换、动态插件、代码加密等场景。

类加载器层次结构:

Bootstrap ClassLoader  (引导类加载器,加载 Java 核心类库)↓
ExtClassLoader  (扩展类加载器,加载 ext 目录下的类)↓
AppClassLoader  (应用类加载器,加载 classpath 下的类)↓
CustomClassLoader (自定义类加载器)

4. 双亲委派模型(Parent Delegation Model)

4.1 什么是双亲委派机制?

JVM 采用双亲委派机制来加载类,即先交给父类加载器加载,如果父类加载器找不到该类,才由当前类加载器加载。这个机制的流程如下:

  1. 一个类加载请求首先被交给父加载器处理。

  2. 如果父加载器找不到(即未加载过该类),则交给子加载器加载。

  3. 如果所有父加载器都找不到,才由当前类加载器尝试加载该类。

4.2 为什么要使用双亲委派?

  • 避免类重复加载:保证 Java 核心类库不会被重复定义。

  • 提高安全性:防止核心 API(如 java.lang.String)被恶意篡改。

  • 减少类加载冲突:确保应用类可以安全地使用 JDK 核心类库。

4.3 破坏双亲委派的情况

某些场景下,开发者可能会打破双亲委派机制,例如:

  • OSGi 模块化系统:不同模块可能需要加载相同类的不同版本。

  • 热部署框架(Tomcat、Spring Boot):支持动态替换类,如 Web 容器的 WebAppClassLoader

  • 自定义类加载器:用于加密解密、动态代理等场景。


5. 自定义类加载器示例

自定义 ClassLoader 需要继承 ClassLoader 并重写 findClass() 方法:

import java.io.*;
​
public class MyClassLoader extends ClassLoader {private String classPath;
​public MyClassLoader(String classPath) {this.classPath = classPath;}
​@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] data = loadClassData(name);return defineClass(name, data, 0, data.length);}
​private byte[] loadClassData(String name) throws ClassNotFoundException {try {String fileName = classPath + name.replace(".", "/") + ".class";InputStream input = new FileInputStream(fileName);ByteArrayOutputStream output = new ByteArrayOutputStream();int ch;while ((ch = input.read()) != -1) {output.write(ch);}input.close();return output.toByteArray();} catch (IOException e) {throw new ClassNotFoundException(name);}}
​public static void main(String[] args) throws Exception {MyClassLoader loader = new MyClassLoader("path/to/classes/");Class<?> clazz = loader.loadClass("com.example.MyClass");Object obj = clazz.getDeclaredConstructor().newInstance();System.out.println(obj.getClass().getName());}
}

总结

本篇博客详细介绍了 JVM 的类加载机制,包括类的生命周期、类加载器的层级结构、双亲委派模型及其应用。理解这些内容,有助于我们优化 Java 应用程序,避免类加载冲突,并实现一些高级特性(如插件化、动态代理等)。

相关文章:

《深入理解JVM》实战笔记(二): 类加载机制与类加载器

序言 Java 语言的强大之处之一在于其动态加载的能力&#xff0c;使得 Java 程序可以在运行时加载新的类&#xff0c;而不需要在编译时确定所有的类信息。这一切都离不开 JVM 的类加载机制。本篇博客将详细探讨 JVM 的类加载过程以及类加载器的工作原理&#xff0c;帮助你更深入…...

ChromeDriver下载

平时为了下个驱动&#xff0c;到处找挺麻烦&#xff0c;收集了很多无偿分享给需要的人&#xff0c;仅供学习和交流。 ChromeDriver 102.0.5005.61 ChromeDriver 105.0.5195.102 ChromeDriver 108.0.5359.71 ChromeDriver 111.0.5563.64 ChromeDriver 116.0.5845.97 Chrom…...

《深度学习实战》第1集:深度学习基础回顾与框架选择

本专栏系列博文旨在帮助读者从深度学习的基础知识逐步进阶到前沿技术&#xff0c;涵盖理论、实战和行业应用。每集聚焦一个核心知识点&#xff0c;并结合实际项目进行实践&#xff0c;避免空谈理论&#xff0c;简洁明快&#xff0c;快速切入代码&#xff0c;所有代码都经过验证…...

Docker 部署AnythingLLM

两个指令搞定 1.下载镜像 docker pull mintplexlabs/anythingllm 2.运行容器 export STORAGE_LOCATION$HOME/anythingllm mkdir -p $STORAGE_LOCATION chmod -R 777 $STORAGE_LOCATION touch "$STORAGE_LOCATION/.env" docker run -d -p 3001:3001 \ --cap-add SY…...

泰山派RK3566移植QT,动鼠标时出现屏幕闪烁

总结&#xff1a; 交叉编译到 泰山派rk3566跑调海康摄像头的qt应用程序失败了。 X11无效窗口。 移植QT注意 屏幕分辨率不要改。改了执行QT的时候&#xff0c;framebuffer识别不出设备。 命令行安装QT-Creator sudo install 类似的指令安装Qt-Creator时&#xff0c;可能找不到编…...

关于Java 反射的简单易懂的介绍

目录 #0.总览 #1. 类的反射 ①介绍 ②获取 ③作用 获取构造函数&#xff1a; 创建实例&#xff1a; 字段操作&#xff1a; 方法操作&#xff1a; 获取修饰符&#xff1a; #2.总结 #0.总览 反射&#xff0c;官方是这样介绍它的&#xff1a; Reflection is a …...

市场趋势中突破确认的多维度判断方法

波动率突破策略是众多交易者广泛采用的重要交易策略之一。而在这一策略中&#xff0c;准确判断突破是否有效&#xff0c;是决定交易成败的关键环节。仅仅依据单一因素来确认突破&#xff0c;往往会使交易者陷入误判的困境&#xff0c;导致不必要的损失。因此&#xff0c;采用多…...

网络空间安全(2)应用程序安全

前言 应用程序安全&#xff08;Application Security&#xff0c;简称AppSec&#xff09;是一个综合性的概念&#xff0c;它涵盖了应用程序从开发到部署&#xff0c;再到后续维护的整个过程中的安全措施。 一、定义与重要性 定义&#xff1a;应用程序安全是指识别和修复应用程序…...

【MyBatis】CRUD、配置解析、ResultMap、分页实现

目录标题 1、Mybatis简介1.1、什么是MyBatis1.2、持久化1.3、持久层1.4、为什么需要MybatisMyBatis的优点 2.1、代码演示搭建实验数据库导入MyBatis相关 jar 包 03、CRUD操作3.1、namespace3.2、select3.3、insert3.4、update3.5、delete 04、MyBatis配置解析4、配置解析4.3、m…...

Linux系统编程之高级信号处理

概述 在前一篇文章中&#xff0c;我们介绍了signal函数、sigaction函数等基本的信号处理方法。在本篇中&#xff0c;我们将介绍信号处理的一些高级用法&#xff0c;包括&#xff1a;阻塞与解除阻塞、定时器等。 阻塞与解除阻塞 有时候&#xff0c;我们不希望某个信号立即被处理…...

深度学习驱动的车牌识别:技术演进与未来挑战

一、引言 1.1 研究背景 在当今社会&#xff0c;智能交通系统的发展日益重要&#xff0c;而车牌识别作为其关键组成部分&#xff0c;发挥着至关重要的作用。车牌识别技术广泛应用于交通管理、停车场管理、安防监控等领域。在交通管理中&#xff0c;它可以用于车辆识别、交通违…...

钉钉快捷免登录 通过浏览器打开第三方系统,

一、钉钉内跳转至浏览器的实现 使用钉钉JSAPI的跳转接口 在钉钉内通过dd.biz.navigation.openLink方法强制在系统浏览器中打开链接。此方法需在钉钉开发者后台配置应用权限&#xff0c;确保应用具备调用该API的资格37。 示例代码&#xff1a; dd.ready(() > {dd.biz.navigat…...

力扣——杨辉三角

题目链接&#xff1a; 链接 题目描述&#xff1a; 思路&#xff1a; 直接找规律&#xff0c;按照数学的思路来 每一行的列最大索引 < 行索引 实现代码&#xff1a; class Solution {public List<List<Integer>> generate(int numRows) {List<List<In…...

stm32108键C-B全调性_动态可视化乐谱钢琴

108键全调性钢琴 一 基本介绍1 项目简介2 实现方式3 项目构成 二 实现过程0 前置基本外设驱动1 声音控制2 乐谱录入&基础乐理3 点阵屏谱点动态刷新4 项目交互控制5 录入新曲子过程 三 展示&#xff0c;与链接视频地址1 主要功能函数一览2 下载链接3 视频效果 一 基本介绍 …...

mysql之规则优化器RBO

文章目录 MySQL 基于规则的优化 (RBO)&#xff1a;RBO 的核心思想&#xff1a;模式匹配与规则应用RBO 的主要优化规则查询重写 (Query Rewrite) / 查询转换 (Query Transformation)子查询优化 (Subquery Optimization) - RBO 的重中之重非相关子查询 (Non-Correlated Subquery)…...

MySQL数据库——表的约束

1.空属性&#xff08;null/not null&#xff09; 两个值&#xff1a;null&#xff08;默认的&#xff09;和not null&#xff08;不为空&#xff09; 数据库默认字段基本都是字段为空&#xff0c;但是实际开发时&#xff0c;尽可能保证字段不为空&#xff0c;因为数据为空没办法…...

vue2.x 中子组件向父组件传递数据主要通过 $emit 方法触发自定义事件方式实现

在 Vue 2.x 中&#xff0c;子组件向父组件传递数据主要通过 自定义事件 的方式实现。具体步骤如下&#xff1a; 1. 子组件通过 $emit 触发事件 子组件可以使用 $emit 方法触发一个自定义事件&#xff0c;并将数据作为参数传递给父组件。 语法&#xff1a; this.$emit(事件名…...

洛谷 P1102 A-B 数对(详解)c++

题目链接&#xff1a;P1102 A-B 数对 - 洛谷 1.题目分析 2.算法原理 解法一&#xff1a;暴力 - 两层for循环 因为这道题需要你在数组中找出来两个数&#xff0c;让这两个数的差等于定值C就可以了&#xff0c;一层for循环枚举A第二层for循环枚举B&#xff0c;求一下看是否等于…...

python用 PythonNet 从 Python 调用 WPF 类库 UI 用XAML

pythonnet 是pythonhe.net通用的神器不多介绍了. 这次这基本上跟python没有关系了. 和winform一样先导包 import clr clr.AddReference("PresentationFramework.Classic, Version3.0.0.0, Cultureneutral, PublicKeyToken31bf3856ad364e35") clr.AddReference(&…...

C++——list模拟实现

目录 前言 一、list的结构 二、默认成员函数 构造函数 析构函数 clear 拷贝构造 赋值重载 swap 三、容量相关 empty size 四、数据访问 front/back 五、普通迭代器 begin/end 六、const迭代器 begin/end 七、插入数据 insert push_back push_front 八、…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...