当前位置: 首页 > 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 八、…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...