《深入理解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 应用程序,避免类加载冲突,并实现一些高级特性(如插件化、动态代理等)。

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

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

相关文章

vin码拍照识别-车牌识别api-vin码接口解析

在当今数字化飞速发展的背景下&#xff0c;如何高效、准确地管理和追踪车辆信息成为了众多企业和个人关注的焦点。VIN码&#xff08;Vehicle Identification Number&#xff09;和车牌作为车辆独一无二的身份标识&#xff0c;在车辆管理、保险理赔、二手车交易等多个场景中发挥…

Tomcat理论(Ⅰ)

目录 服务器流程图一览 一、JavaWeb前奏(了解) 1. C/S结构 2. B/S结构 3. 静态网页&动态网页 4.常见的网页 5.Web服务器 知名服务器&#xff1a; ​编辑 二、Tomcat安装&#xff08;熟练&#xff09; 1.Tomcat概述 2.Tomcat的作用 3.Tomcat安装 4.Tomcat测试 3.…

[实现Rpc] 通信-Muduo库的实现 | 完美转发 | reserve | unique_lock

目录 MudouBuffer ⭕右值引用 | 完美转发 右值引用 完美转发 实现原理 结合右值引用和完美转发的例子 LVProtocol ⭕vector 的 reserve 函数 1. 背景 2. reserve 函数原型 3. 示例代码 4. 输出结果 5. 结果解析 6. 关键点说明 MuduoConnection ⭕mudou 库 &am…

[OD E 100] 生成哈夫曼树

题目 题目描述 给定长度为 n 的无序的数字数组&#xff0c;每个数字代表二叉树的叶子节点的权值&#xff0c;数字数组的值均大于等于 1 。请完成一个函数&#xff0c;根据输入的数字数组&#xff0c;生成哈夫曼树&#xff0c;并将哈夫曼树按照中序遍历输出。 为了保证输出的二…

网络安全知识:网络安全概念、内容和主要技术纵览

21世纪全世界的计算机都将通过Internet联到一起&#xff0c;随着Internet的发展&#xff0c;网络丰富的信息资源给用户带来了极大的方便&#xff0c;但同时也给上网用户带来了安全问题。由于Internet的开放性和超越组织与国界等特点&#xff0c;使它在安全性上存在一些隐患。而…

【机器学习】多元线性回归算法和正规方程解求解

多元线性方差和正规方差解 一、摘要二、多元线性回归介绍三、正规方程解的求解及代码实现 一、摘要 本文围绕多元线性回归的正规方程解展开&#xff0c;为初学者系统介绍了相关基本概念、求解方法、实际应用以及算法封装要点。 首先&#xff0c;深入阐释了正规方程解这一多元…

Arcmap和ArcgisPro重装及配置迁移

近期要重装一下ArcgisPro&#xff0c;在此记录并作为大家的借鉴 1.备份配置文件&#xff1a;其中Desktop10.8为Arcmap的配置文件 2.通过控制面板卸载&#xff0c;arcpro卸载时间较长&#xff0c;先将语言包等卸载&#xff0c;最后再卸载5G主程序&#xff0c;有些文章会介绍清理…

【天线】IFA天线知识点摘抄

MIFA天线的尺寸与性能关系 1&#xff0c;辐射效率 天线越小&#xff0c;辐射效率越低。唯一好处是减少PCB占用空间 2&#xff0c;带宽 一般MIFA天线在2.4G频段内的带宽&#xff1a;S11≤-10dB的范围为2.44GHz230MHz。较小的尺寸可能会限制带宽 3&#xff0c;增益 MIFA天线的…

路由基本配置

学习目标 • 根据拓扑图进行网络布线。 • 清除启动配置并将路由器重新加载为默认状态。 • 在路由器上执行基本配置任务。 • 配置并激活以太网接口。 • 测试并检验配置。 • 思考网络实施方案并整理成文档。 任务 1&#xff1a;网络布线 使用适当的电缆类型连接网络设备。…

力扣27. 移除元素(快慢指针)

Problem: 27. 移除元素 文章目录 题目描述思路Code 题目描述 思路 定义快慢指针均指向数组起始位置&#xff0c;当fast指针指向的元素不等于val时将fast指针指向的元素赋值给slow并让slow指针向前移动&#xff0c;fast指针一直向前移动 时间复杂度: O ( n ) O(n) O(n); 空间复杂…

jemalloc 5.3.0里的快速路径分配逻辑及可借鉴的高性能编程思路

一、背景 jemalloc 5.3.0的介绍&#xff0c;我们已经持续了一段时间了&#xff0c;在 jemalloc 5.3.0的tsd模块的源码分析-CSDN博客 博客里&#xff0c;我们介绍了jemalloc的编译和调试&#xff0c;在 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展…

Vue前端开发-Vant之Layout组件

在Vant 中&#xff0c;Layout组件用于元素的响应式布局&#xff0c;分别由van-row和van-col两个组件来实现&#xff0c;前者表示行&#xff0c;后者被包裹在van-row组件中&#xff0c;表示列&#xff0c;共有24列栅格组成&#xff0c;在van-col组件中&#xff0c;span属性表示所…

【UCB CS 61B SP24】Lecture 5 - Lists 3: DLLists and Arrays学习笔记

本文内容为构建双向循环链表、使用 Java 的泛型将其优化为通用类型的链表以及数组的基本语法介绍。 1. 双向链表 回顾上一节课写的代码&#xff0c;当执行 addLast() 与 getLast() 方法时需要遍历链表&#xff0c;效率不高&#xff0c;因此可以添加一个指向链表末尾的索引&am…

Ubuntu 22.04 Install deepseek

前言 deepseekAI助手。它具有聊天机器人功能&#xff0c;可以与用户进行自然语言交互&#xff0c;回答问题、提供建议和帮助解决问题。DeepSeek 的特点包括&#xff1a; 强大的语言理解能力&#xff1a;能够理解和生成自然语言&#xff0c;与用户进行流畅的对话。多领域知识&…

边缘安全加速(ESA)套餐

为帮助不同规模和需求的企业选择合适的解决方案&#xff0c;边缘安全加速&#xff08;ESA&#xff09;提供了多种套餐。以下是四种主要套餐的介绍&#xff0c;每个套餐都根据企业需求提供不同的功能和服务水平&#xff0c;从基础安全保护到企业级的全面防护与加速。 1. 各版本详…

I²C简介

前言 IC&#xff08;Inter-Integrated Circuit, 内置集成电路&#xff09;总线是由Philips公司&#xff08;现属于恩智浦&#xff09;在上世纪80年代开发的两线式串行通信总线&#xff0c;用于连接微控制器及其外围设备&#xff0c;控制设备之间的通信。 IC总线的物理拓扑示意…

Frp部署文档

Frp部署文档 开源项目地址:https://github.com/fatedier/frp项目中文文档地址&#xff1a;https://github.com/fatedier/frp/blob/dev/README_zh.md官网文档地址: https://gofrp.org/zh-cn/docs/发布包地址&#xff1a;https://github.com/fatedier/frp/releases 要注意对应的…

ArcGIS Pro进行坡度与坡向分析

在地理信息系统中&#xff0c;坡度分析是一项至关重要的空间分析方法&#xff0c;旨在精确计算地表或地形的坡度&#xff0c;为地形特征识别、土地资源规划、环境保护、灾害预警等领域提供科学依据。本文将详细介绍如何利用ArcGIS Pro这一强大的地理信息系统软件&#xff0c;进…

从卡顿到丝滑:火山引擎DeepSeek-R1引领AI工具新体验

方舟大模型体验中心全新上线&#xff0c;免登录体验满血联网版Deep Seek R1 模型及豆包最新版模型:https://www.volcengine.com/experience/ark?utm_term202502dsinvite&acDSASUQY5&rcGO9H7M38 告别DeepSeek卡顿&#xff0c;探索火山引擎DeepSeek-R1的丝滑之旅 在A…

Python的那些事第二十八篇:数据分析与操作的利器Pandas

Pandas:数据分析与操作的利器 摘要 Pandas是基于Python的开源数据分析库,广泛应用于数据科学、机器学习和商业智能等领域。它提供了高效的数据结构和丰富的分析工具,能够处理结构化数据、时间序列数据以及复杂的数据转换任务。本文从Pandas的基础概念入手,深入探讨其核心…