JVM 双亲委派机制
一、从 JDK 到 JVM:Java 运行环境的基石
在 Java 开发领域,JDK(Java Development Kit)是开发者的核心工具包。它不仅包含了编译 Java 代码的工具(如 javac),还内置了 JRE(Java Runtime Environment)—— 即 Java 程序的运行时环境。而 JVM(Java Virtual Machine)则是 JRE 的核心,它如同一个 “翻译官”,将 Java 字节码转换为不同操作系统能理解的机器指令,实现了 “一次编写,到处运行” 的跨平台特性。
1.JVM 作用
Java 虚拟机负责装载字节码到其内部,解释/编译为对应平台上的机器码指令执行。
现在的 JVM 不仅可以执行 java 字节码文件,还可以执行其他语言编译后的字节码文件,是一个跨语言平台.
程序在执行之前先要把 java 代码转换成字节码(class 文件),jvm首先需要把字节码通过一定的方式 类加载器(ClassLoader)把文件加载到内存中的运行时数据区(Runtime Data Area) ,而字节码文件是jvm的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU 去执行,而这个过程中需要调用其他语言的接口本地库接口(NativeInterface) 来实现整个程序的功能,这就是这 4 个主要组成部分的职责与功能。
比如,当我们运行java HelloWorld
时,JVM 首先通过类加载器找到HelloWorld.class
文件,将二进制数据读入内存,创建HelloWorld.class
对象。
二、JVM 模块划分与核心功能
JVM 的架构可分为四大模块:类加载器子系统、运行时数据区、执行引擎和本地方法接口。
1. 类加载器子系统
类加载器负责将字节码文件加载到 JVM 中。根据职责不同,JVM 提供了三种类加载器:
- 启动类加载器(Bootstrap ClassLoader):用 C/C++ 实现,加载 Java 核心类库(如
rt.jar
),位于%JAVA_HOME%/lib
目录。- 扩展类加载器(Extension ClassLoader):加载
%JAVA_HOME%/jre/lib/ext
目录或java.ext.dirs
指定路径的类库。- 应用程序类加载器(Application ClassLoader):加载用户类路径(classpath)下的类,是程序默认的类加载器。
类加载器采用双亲委派机制:当一个类加载器收到加载请求时,会先委托父类加载器处理,只有父类无法加载时才尝试自己加载。这一机制确保了核心类的安全性和唯一性。例如,当尝试加载java.lang.String
时,启动类加载器会优先加载核心库中的 String 类,避免用户自定义类覆盖核心类。
2. 运行时数据区
运行时数据区是 JVM 在执行程序时分配的内存区域,包含以下部分:
- 程序计数器:记录当前线程执行的字节码指令地址,是线程私有的最小内存空间。
- Java 虚拟机栈:每个线程创建时生成,保存方法调用的栈帧(包含局部变量表、操作数栈、方法返回地址等),线程私有,可能出现栈溢出(StackOverflowError)。
- 本地方法栈:管理本地方法(如 C/C++ 实现的方法)的调用。
- 堆内存:存储对象实例,是 GC(垃圾回收)的主要区域,分为新生代(Eden 和 Survivor 区)和老年代,通过分代收集算法优化回收效率。
- 方法区:存储类的元数据(如字节码、静态变量、常量池),JDK8 后称为元空间(Metaspace),逻辑上独立于堆。
3. 执行引擎
执行引擎是 JVM 的 “大脑”,负责将字节码转换为机器指令。它包含:
- 解释器:逐行解释执行字节码,启动快但效率较低。
- JIT 编译器:将频繁执行的 “热点代码” 编译为本地机器码,存储在方法区的 JIT 缓存中,提升执行效率。
- 垃圾回收器:自动回收不再使用的对象,主要针对堆内存,采用标记 - 复制、标记 - 清除、标记 - 压缩等算法。
4. 本地方法接口
本地方法接口允许 Java 调用非 Java 代码(如 C/C++),通过本地方法库实现与操作系统或硬件的交互,例如文件操作、网络通信等。
三、类加载的核心机制与实践
1. 类加载的作用与过程
类加载的核心任务是将字节码文件转换为 JVM 可识别的 Class 对象。这一过程分为五个阶段:
- 加载(Loading):通过类的全限定名获取二进制字节流,生成 Class 对象。
- 验证(Verification):检查字节码的安全性和合规性,防止恶意代码攻击。
- 准备(Preparation):为静态变量分配内存并设置默认初始值(如 int 初始化为 0)。
- 解析(Resolution):将符号引用转换为直接引用(如将类名转换为内存地址)。
- 初始化(Initialization):执行静态代码块和静态变量赋值,这是类加载的最后一步。
2. 类加载的触发时机
类加载遵循 “按需加载” 原则,只有在需要使用类时才会触发。根据 JVM 规范,以下情况会强制加载类(主动引用):
假设我们有一个 Hello 类
package com.ffyc.classload;/*** 问题:什么时候类会被加载?**/
public class Hello {// 作为静态成员时,类会被加载static {System.out.println("类被加载了......");}//作为main方法时,类也会被加载public static void main(String[] args) {System.out.println("1111111");}}
TestHello 类
package com.ffyc.classload;public class TestHello {public static void main(String[] args) throws ClassNotFoundException {new Hello(); // 触发Hello类的初始化,并加载Hello类Class.forName("com.ffyc.classload.Hello");//反射方式加载Hello类}}
3. 类加载的典型案例
(1) 主动引用(必定触发加载)
new
实例化对象MyClass obj = new MyClass(); // 首次创建对象时加载
- 访问类的静态变量或静态方法
int value = MyClass.staticField; // 访问静态字段 MyClass.staticMethod(); // 调用静态方法
- 反射调用(
Class.forName()
)Class.forName("com.example.MyClass"); // 通过反射强制加载
- 初始化子类时(父类优先加载)
class Parent {} class Child extends Parent {} // 首次使用 Child 时,会先加载 Parent
- 作为程序入口的主类(
main
方法所在类)public class Main { public static void main(String[] args) {} // JVM 启动时加载 }
(2) 被动引用(不会触发加载)
- 通过子类引用父类的静态字段
class Parent { static int value = 10; } class Child extends Parent {} System.out.println(Child.value); // 仅加载 Parent,不加载 Child
- 通过数组定义类
MyClass[] arr = new MyClass[10]; // 不会加载 MyClass
- 引用常量(常量在编译期优化)
class MyClass { final static int VALUE = 10; } System.out.println(MyClass.VALUE); // 不触发加载(常量池直接访问)
四、类加载器分类
站在 java 开发人员的角度来看,类加载器就应当划分得更细致一些. 保持者三层类加载器.
1. 启动类加载器 (BootStrap ClassLoader)
这个类加载器使用 C/C++语言实现,也叫引导类加载器,嵌套在 JVM 内部.它用来加载java 核心类库.负责加载扩展类加载器和应用类加载器,并为他们指定父类加载器. 出于安全考虑,启动类加载器只加载存放在\lib 目录,或者被-Xbootclasspath 参数锁指定的路径中存储放的类.
2. 扩展类加载器(Extension ClassLoader)
Java 语言编写的,由 sun.misc.Launcher$ExtClassLoader 实现. 派生于 ClassLoader 类. 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从JDK 系统安装目录的jre/lib/ext 子目录(扩展目录)下加载类库.如果用户创建的jar 放在此目录下,也会自动由扩展类加载器加载.
3. 应用程序类加载器(系统类加载器 Application ClassLoader)
Java 语言编写的,由 sun.misc.Launcher$AppClassLoader 实现.派生于 ClassLoader 类. 加载我们自己定义的类,用于加载用户类路径(classpath)上所有的类. 该类加载器是程序中默认的类加载器.ClassLoader 类 , 它 是 一 个 抽 象 类 , 其 后 所 有的类加载器都继承自ClassLoader(不包括启动类加载器)
五、双亲委派机制
Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要该类时才会将它的 class 文件加载到内存中生成 class 对象.而且加载某个类的class 文件时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式.
工作原理:
1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行.
2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器.
3. 如果父类加载器可以完成类的加载任务,就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制. 如果均加载失败,就会抛出 ClassNotFoundException 异常。
那么思考一下,如果我们自定义一个 String 类,会被加载吗?
直接上示例去验证你的答案
package java.lang;/*** 测试双亲委派机制*/
public class String {static {System.out.println("自定义 String 类被加载!");}
}
package com.ffyc.classload;public class TestHello {public static void main(String[] args) {new Hello(); // 触发Hello类的初始化,并加载Hello类try {Class.forName("com.ffyc.classload.Hello");//反射方式加载Hello类Class.forName("java.lang.String");} catch (ClassNotFoundException e) {throw new RuntimeException(e);}System.out.println("String类加载器为:"+ String.class.getClassLoader()+"所以属于启动类加载器");System.out.println("Hello类加载器为:"+Hello.class.getClassLoader()+"所以属于系统类加载器");}}
输出结果为,并没有看到 "自定义 String 类被加载!" 这句话
可以看到,自定义的String 类虽然和jdk的String类同包同名,但还是没有被加载,这就是双亲委派机制,那么问题来了,我就想让它加载我自己定义的String类,该怎么做?
六、如何打破双亲委派机制
Java 虚拟机的类加载器本身可以满足加载的要求,但是也允许开发者自定义类加载器。 在 ClassLoader 类中涉及类加载的方法有两个,loadClass(String name), findClass(String name),这两个方法并没有被 final 修饰,也就表示其他子类可以重写. 重写 findClass 方法 我们可以通过自定义类加载重写方法打破双亲委派机制, 再例如 tomcat 等都有自己定义的类加载器.
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;public class CustomClassLoader extends ClassLoader {private final String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 检查是否已加载过该类Class<?> loadedClass = findLoadedClass(name);if (loadedClass != null) {return loadedClass;}// 打破双亲委派:先尝试自己加载,再委托父类try {// 自定义加载逻辑(例如从指定路径加载类)byte[] classBytes = loadClassBytes(name);if (classBytes != null) {return defineClass(name, classBytes, 0, classBytes.length);}} catch (IOException e) {// 加载失败,继续委托父类加载器}// 委托父类加载器(保留原有机制的兜底)return super.loadClass(name, resolve);}}private byte[] loadClassBytes(String className) throws IOException {// 将类名转换为文件路径(例如com.example.MyClass → /path/com/example/MyClass.class)String path = classPath + File.separator + className.replace('.', File.separatorChar) + ".class";File file = new File(path);if (!file.exists()) {return null;}try (FileInputStream fis = new FileInputStream(file)) {byte[] bytes = new byte[(int) file.length()];fis.read(bytes);return bytes;}}
}
public class Main {public static void main(String[] args) throws Exception {// 创建自定义类加载器,指定加载路径CustomClassLoader loader = new CustomClassLoader("/path/to/classes");// 加载自定义类(优先从指定路径加载)Class<?> clazz = loader.loadClass("com.example.MyClass");Object instance = clazz.newInstance();System.out.println(instance.getClass().getClassLoader()); // 输出:CustomClassLoader}
}
七、总结
JVM 的类加载机制是 Java 程序运行的基石,它通过类加载器、运行时数据区和执行引擎的协同工作,确保了程序的跨平台性和高效执行。理解类加载的过程、时机和类加载器的工作原理,不仅能帮助开发者优化程序性能,还能深入排查类冲突、内存泄漏等问题。无论是日常开发还是高级调优,掌握 JVM 类加载机制都是成为优秀 Java 工程师的必经之路。
相关文章:

JVM 双亲委派机制
一、从 JDK 到 JVM:Java 运行环境的基石 在 Java 开发领域,JDK(Java Development Kit)是开发者的核心工具包。它不仅包含了编译 Java 代码的工具(如 javac),还内置了 JRE(Java Run…...

uniapp -- uCharts 仪表盘刻度显示 0.9999999 这样的值问题处理。
文章目录 🍉问题🍉解决方案🍉问题 在仪表盘上,23.8变成了 23.799999999999997 🍉解决方案 formatter格式化问题 1:在 config-ucharts.js 或 config-echarts.js 配置对应的 formatter 方法 formatter: {yAxisDemo1: function (...

BGP团体属性
团体属性: 1、用于限制BGP路由的传递范围 2、类似于IGP协议中的tag值,用于对BGP路由实现标记。 团体属性的分类: 1、公共团体属性: Internet:默认所有路由都有该属性,具有该属性BGP路由发送给所有的BGP邻居…...

Redis——三大策略
过期删除策略 Redis可以对key设置过期时间,因此需要有相应的机制将已过期的键值对删除 设置了过期时间的key会存放在过期字典中,可以用presist命令取消key过期时间 过期字典存储在redisDb结构中: typedef struct redisDb {dict *dict; …...

Windows 操作系统使用 Tcping 命令检查目标主机端口是否开放
检查目标主机端口是否开放的方法已经很多了,网络上也有第三方网页版的检查工具,这篇文章给大家介绍一个实用小工具 Tcping 。 一、下载安装 Tcping 命令 Tcping 非 Windows 自带命令,我们需要下载 Tcping 可执行文件,然后将该文…...
序列化和反序列化:从理论到实践的全方位指南
你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…...
PDF Base64格式字符串转换为PDF文件临时文件
需求描述: 在对接电子病历系统与河北CA,进行免密文件签章的时候,两者系统入参不同,前者是pdf文件,base64格式;后者要求File类型的PDF文件。 在业务中间层开发时,则需要接收EMR侧提供的base64格式…...

开源RTOS(实时操作系统):nuttx 编译
开源RTOS(实时操作系统):nuttx 编译 手册:Installing — NuttX latest documentation 源码:GitHub - apache/nuttx: Apache NuttX is a mature, real-time embedded operating system (RTOS) Installing The fir…...

python打包exe报错:处理文件时错误:Excel xlsx file; not supported
背景:最近用python写一个excel解析工具,然后打包成exe可执行文件的时候,遇到这样的问题 1.在我自己编译器运行是可以正常将上传后的excel进行解析,但是在打包成exe后,就无法正常解析excel 问题排查: 1.切换…...

VUE3 -综合实践(Mock+Axios+ElementPlus)
目录 前言 目标 1.工程创建 2.Mock 2.1 配置Mock 扩 展 2.2 定义模拟数据 2.3 创建Mock服务器 3.导入ElementPlus 4.表格页面搭建 5.动态路由跳转 6.详情页面的制作 前言 基于前文 VUE3详细入门,我们对VUE3的基本使用有了初步的了解,下…...

NDS3211HV单路H.264/HEVC/HD视频编码器
1产品概述 NDS3211HV单路高清编码器是一款功能强大的音/视频编码设备,支持2组立体声,同时还支持CC(CVBS)字幕。支持多种音频编码方式。该设备配备了多种音/视频输入接口:HD-SDI数字视频输入、HDMI高清输入(支持CC)、A…...

LeetCode热题100--206.反转链表--简单
1. 题目 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1] 示例 2: 输入:head [1,2] 输出:[2,1] 示例 3&…...

来一个复古的技术FTP
背景 10年前的老代码,需要升级springboot框架,在升级过程中,测试业务流程里,有FTP的下载业务,不管测试环境如何测试,都没有成功,最后只能自己搭建一个FTP服务器,写一个ftp-demo来测试…...
OpenCV CUDA模块中矩阵操作------分布统计类
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 OpenCV 的 CUDA 模块中,meanStdDev 函数用于计算矩阵的平均值(Mean)和标准差(StdDevÿ…...

OpenWebUI新突破,MCPO框架解锁MCP工具新玩法
大家好,Open WebUI 迎来重要更新,现已正式支持 MCP 工具服务器,但 MCP 工具服务器需由兼容 OpenAPI 的代理作为前端。mcpo 是一款实用代理,经测试,它能让开发者使用 MCP 服务器命令和标准 OpenAPI 服务器工具ÿ…...
go.mod关于go版本异常的处理
1.私有仓库 go.mod 要注意module的配置mod地址,要与下载地址一致。 否则就算下载下来,就会比较后报错。 module test.com/devGroup/devProjectgo 1.22.2 2. 代码中的包引用地址。 要与module中的mod路径一致 package mainimport ("module …...

TRTC实时对话式AI解决方案,助力人机语音交互极致体验
近年来,AI热度持续攀升,无论是融资规模还是用户热度都大幅增长。2023 年,中国 AI 行业融资规模达2631亿人民币,较2022年上升51%;2024年第二季度,全球 AI 初创企业融资规模为 240 亿美金,较第一季…...

Linux安全篇 --firewalld
一、Firewalld 防火墙概述 1、Firewalld 简介 firewalld 的作用是为包过滤机制提供匹配规则(或称为策略),通过各种不同的规则告诉netfilter 对来自指定源、前往指定目的或具有某些协议特征的数据包采取何种处理方式为了更加方便地组织和管理防火墙,firewalld 提供…...

系分论文《论系统需求分析方法及应用》
系统分析师论文范文系列 【摘要】 2022年6月,我作为系统分析师参与了某金融机构“智能信贷风控系统”的建设项目。该系统旨在通过对业务流程的数字化重构,优化信贷审批效率并降低风险。项目涉及信贷申请、资质审核、风险评估、额度审批等核心流程&#x…...

LIIGO ❤️ RUST: 12 YEARS
LIIGO 💖 RUST: 12 YEARS 今天是RUST语言1.0发布十周年纪念日。十年前的今天,2015年的今天,Rust 1.0 正式发行。这是值得全球Rust支持者隆重纪念的日子。我借此机会衷心感谢Rust语言创始人Graydon Hoare,Mozilla公司,…...
SQL、Oracle 和 SQL Server 的比较与分析
SQL、Oracle 和 SQL Server 的比较与分析 一、基础概念 1. SQL (Structured Query Language) 定义:结构化查询语言,用于管理关系型数据库的标准语言类型: DDL (数据定义语言):CREATE, ALTER, DROPDML (数据操作语言)࿱…...

Trivy:让你时刻掌控的开源安全扫描器
深入了解 Trivy:全面的安全扫描工具 在如今互联网快速发展的时代,软件的安全性显得尤为重要。随着应用程序的复杂性增加,其可能带来的安全漏洞也在不断增多。如何快速、准确地发现这些潜在威胁是每个开发者和运维人员心中的课题。今天,我们将为大家介绍一个开源的安全扫描…...

LlamaIndex 第八篇 MilvusVectorStore
本指南演示了如何使用 LlamaIndex 和 Milvus 构建一个检索增强生成(RAG)系统。 RAG 系统将检索系统与生成模型相结合,根据给定的提示生成新的文本。该系统首先使用 Milvus 等向量相似性搜索引擎从语料库中检索相关文档,然后使用生…...

2022河南CCPC(前四题)
签到题目 #include <bits/stdc.h> using namespace std; #define int long long #define PII pair<int,int> #define fi first #define se second #define endl \n #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);void solve() {int n;cin>>…...

谷歌浏览器(Google Chrome)136.0.7103.93便携增强版|Win中文|安装教程
软件下载 【名称】:谷歌浏览器(Google Chrome)136.0.7103.93 【大小】:170M 【语言】:简体中文 【安装环境】:Win10/Win11 【夸克网盘下载链接】(务必手机注册): h…...

高可用消息队列实战:AWS SQS 在分布式系统中的核心解决方案
引言:消息队列的“不可替代性” 在微服务架构和分布式系统盛行的今天,消息队列(Message Queue) 已成为解决系统解耦、流量削峰、异步处理等难题的核心组件。然而,传统的自建消息队列(如RabbitMQ、Kafka&am…...

「Mac畅玩AIGC与多模态41」开发篇36 - 用 ArkTS 构建聚合搜索前端页面
一、概述 本篇基于上一节 Python 实现的双通道搜索服务(聚合 SearxNG 本地知识库),构建一个完整的 HarmonyOS ArkTS 前端页面。用户可在输入框中输入关键词,实时查询本地服务 http://localhost:5001/search?q...,返…...

springCloud/Alibaba常用中间件之Seata分布式事务
文章目录 SpringCloud Alibaba:依赖版本补充Seata处理分布式事务(AT模式)AT模式介绍核心组件介绍AT的工作流程:两阶段提交(**2PC**) Seata-AT模式使用Seata(2.0.0)下载、配置和启动Seata案例实战前置代码添加全局注解 GlobalTransactional Sp…...

Datawhale FastAPI Web框架5月第1次笔记
原课程地址: FastAPI Web框架https://www.datawhale.cn/learn/summary/164本次难点: 切换python的版本为3.10 作业过程 启动: jupyter notebook 首先我们要确保自己的python版本是3.10 import sys print(sys.version) 第一个fastapi…...

操作系统:os概述
操作系统:OS概述 程序、进程与线程无极二级目录三级目录 程序、进程与线程 指令执行需要那些条件?CPU内存 需要数据和 无极 二级目录 三级目录...